1;;;; web-client.test --- HTTP client       -*- mode: scheme; coding: utf-8; -*-
2;;;;
3;;;; 	Copyright (C) 2013 Free Software Foundation, Inc.
4;;;;
5;;;; This library is free software; you can redistribute it and/or
6;;;; modify it under the terms of the GNU Lesser General Public
7;;;; License as published by the Free Software Foundation; either
8;;;; version 3 of the License, or (at your option) any later version.
9;;;;
10;;;; This library is distributed in the hope that it will be useful,
11;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13;;;; Lesser General Public License for more details.
14;;;;
15;;;; You should have received a copy of the GNU Lesser General Public
16;;;; License along with this library; if not, write to the Free Software
17;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19
20(define-module (test-suite web-client)
21  #:use-module (web client)
22  #:use-module (web request)
23  #:use-module (web response)
24  #:use-module (ice-9 iconv)
25  #:use-module (ice-9 binary-ports)
26  #:use-module (test-suite lib))
27
28
29(define get-request-headers:www.gnu.org/software/guile/
30  "GET /software/guile/ HTTP/1.1
31Host: www.gnu.org
32Connection: close
33
34")
35
36(define get-response-headers:www.gnu.org/software/guile/
37  "HTTP/1.1 200 OK
38Date: Fri, 11 Jan 2013 10:59:11 GMT
39Server: Apache/2.2.14
40Accept-Ranges: bytes
41Cache-Control: max-age=0
42Expires: Fri, 11 Jan 2013 10:59:11 GMT
43Vary: Accept-Encoding
44Content-Length: 8077
45Connection: close
46Content-Type: text/html
47Content-Language: en
48
49")
50
51(define get-response-body:www.gnu.org/software/guile/
52  "<!DOCTYPE html PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
53<html>
54<head>
55  <title>GNU Guile (About Guile)</title>
56  <link rel=\"stylesheet\" type=\"text/css\" href=\"/gnu.css\">
57  <link rel=\"stylesheet\" type=\"text/css\" href=\"/software/guile/guile.css\">
58  <link rev=\"made\" href=\"mailto:bug-guile@gnu.org\">
59</head>
60
61<!-- If you edit these html pages directly, you're not doing yourself any
62     favors - these pages get updated programaticly from a pair of files. Edit
63     the files under the template directory instead -->
64
65<!-- Text black on white, unvisited links blue, visited links navy,
66     active links red -->
67
68<body bgcolor=\"#ffffff\" text=\"#000000\" link=\"#1f00ff\" alink=\"#ff0000\" vlink=\"#000080\">
69  <a name=\"top\"></a>
70  <table cellpadding=\"10\">
71    <tr>
72      <td>
73\t<a href=\"/software/guile/\">
74\t  <img src=\"/software/guile/graphics/guile-banner.small.png\" alt=\"Guile\">
75\t</a>
76      </td>
77      <td valign=\"bottom\">
78\t<h4 align=\"right\">The GNU extension language</h4>
79\t<h4 align=\"right\">About Guile</h4>
80      </td>
81    </tr>
82  </table>
83  <br />
84  <table border=\"0\">
85
86    <!-- Table with 2 columns.  One along the left (navbar) and one along the
87\t right (body). On the main page, the left links to anchors on the right,
88\t or to other pages.  The left has 2 sections. Top is global navigation,
89\t the bottom is local nav. -->
90
91    <tr>
92      <td class=\"sidebar\">
93\t<table cellpadding=\"4\">
94\t  <tr>
95\t    <!-- Global Nav -->
96
97\t    <td nowrap=\"\">
98\t      <p><b>About Guile</b><br />
99\t\t<a href=\"/software/guile/guile.html\">What is Guile?</a><br />
100\t\t<a href=\"/software/guile/news.html\">News</a><br />
101\t\t<a href=\"/software/guile/community.html\">Community</a><br />
102\t      </p>
103\t
104\t      <p><b>Documentation</b><br />
105\t\t<a href=\"/software/guile/docs/docs.html\">Manuals</a><br />
106\t\t<a href=\"/software/guile/docs/faq/guile-faq.html\">FAQ's</a><br />
107\t      </p>
108
109\t      <p><b>Download</b><br />
110\t\t<a href=\"/software/guile/download.html#releases\">Releases</a><br />
111\t\t<a href=\"/software/guile/download.html#git\">Repository</a><br />
112\t\t<a href=\"/software/guile/download.html#snapshots\">Snapshots</a><br />
113\t      </p>
114
115\t      <p><b>Projects</b><br />
116\t\t<a href=\"/software/guile/gnu-guile-projects.html#Core\">Core</a><br />
117\t\t<a href=\"/software/guile/gnu-guile-projects.html#GUI\">GUI</a><br />
118\t\t<a href=\"/software/guile/gnu-guile-projects.html#File-Formats\">File Formats</a><br />
119\t\t<a href=\"/software/guile/gnu-guile-projects.html#Networking\">Networking</a><br />
120\t\t<a href=\"/software/guile/gnu-guile-projects.html#Tools\">Tools</a><br />
121\t\t<a href=\"/software/guile/gnu-guile-projects.html#Applications\">Applications</a><br />
122\t      </p>
123\t
124\t      <p><b>Development</b><br />
125\t\t<a href=\"http://savannah.gnu.org/projects/guile/\">Project summary</a><br />
126\t\t<a href=\"/software/guile/developers.html\">Helping out</a><br />
127\t\t<a href=\"/software/guile/ideas.html\">Cool ideas</a><br />
128\t      </p>
129
130\t      <p><b>Resources</b><br>
131\t\t<a href=\"/software/guile/resources.html#guile_resources\">Guile Resources</a><br />
132\t\t<a href=\"/software/guile/resources.html##scheme_resources\">Scheme Resources</a><br />
133\t      </p>
134\t    </td>
135\t  </tr>
136\t  <tr>
137
138\t    <!-- Global Nav End -->
139\t
140  <tr>
141    <td>
142      <p><a href=\"http://www.gnu.org/\">GNU Project home page</a></p>
143      <p><a href=\"#whatisit\">What is Guile?</a></p>
144      <p><a href=\"#get\">Getting Guile</a></p>
145    </td>
146  </tr>
147
148
149\t  </tr>
150\t</table>
151      </td>
152
153      <td class=\"rhs-body\">
154
155\t
156  <a name=\"whatisit\"><h3 align=\"left\">What is Guile? What can it do for you?</h3></a>
157  <p>
158    Guile is the <em>GNU Ubiquitous Intelligent Language for Extensions</em>,
159    the official extension language for the
160    <a href=\"http://www.gnu.org/\">GNU operating system</a>.
161  </p>
162
163  <p>
164    Guile is a library designed to help programmers create flexible
165    applications.  Using Guile in an application allows the application's
166    functionality to be <em>extended</em> by users or other programmers with
167    plug-ins, modules, or scripts.  Guile provides what might be described as
168    \"practical software freedom,\" making it possible for users to customize an
169    application to meet their needs without digging into the application's
170    internals.
171  </p>
172
173  <p>
174    There is a long list of proven applications that employ extension languages.
175    Successful and long-lived examples of Free Software projects that use
176    Guile are <a href=\"http://www.texmacs.org/\">TeXmacs</a>,
177    <a href=\"http://lilypond.org/\">LilyPond</a>, and
178    <a href=\"http://www.gnucash.org/\">GnuCash</a>.
179  </p>
180
181  <h3>Guile is a programming language</h3>
182
183  <p>
184    Guile is an interpreter and compiler for
185    the <a href=\"http://schemers.org/\">Scheme</a> programming language, a clean
186    and elegant dialect of Lisp.  Guile is up to date with recent Scheme
187    standards, supporting the
188    <a href=\"http://www.schemers.org/Documents/Standards/R5RS/\">Revised<sup>5</sup></a>
189    and most of the <a href=\"http://www.r6rs.org/\">Revised<sup>6</sup></a> language
190    reports (including hygienic macros), as well as many
191    <a href=\"http://srfi.schemers.org/\">SRFIs</a>.  It also comes with a library
192    of modules that offer additional features, like an HTTP server and client,
193    XML parsing, and object-oriented programming.
194  </p>
195
196  <h3>Guile is an extension language platform</h3>
197
198  <p>
199    Guile is an efficient virtual machine that executes a portable instruction
200    set generated by its optimizing compiler, and integrates very easily with C
201    and C++ application code.  In addition to Scheme, Guile includes compiler
202    front-ends for
203    <a href=\"http://www.ecma-international.org/publications/standards/Ecma-262.htm\">ECMAScript</a>
204    and <a href=\"http://www.emacswiki.org/cgi-bin/wiki?EmacsLisp\">Emacs Lisp</a>
205    (support for <a href=\"http://www.lua.org/\">Lua</a> is underway), which means
206    your application can be extended in the language (or languages) most
207    appropriate for your user base.  And Guile's tools for parsing and compiling
208    are exposed as part of its standard module set, so support for additional
209    languages can be added without writing a single line of C.
210  </p>
211
212  <h3>Guile gives your programs more power</h3>
213
214  <p>
215    Using Guile with your program makes it more usable.  Users don't
216    need to learn the plumbing of your application to customize it; they just
217    need to understand Guile, and the access you've provided.  They can easily
218    trade and share features by downloading and creating scripts, instead of
219    trading complex patches and recompiling their applications.  They don't need
220    to coordinate with you or anyone else.  Using Guile, your application has a
221    full-featured scripting language right from the beginning, so you can focus
222    on the novel and attention-getting parts of your application.
223  </p>
224
225  <a name=\"get\"><h2 align=\"center\">How do I get Guile?</h2></a>
226
227  <ul>
228    <li>The current <em>stable</em> release is
229      <a href=\"ftp://ftp.gnu.org/gnu/guile/guile-2.0.7.tar.gz\">2.0.7</a>.
230    </li>
231  </ul>
232
233  <p>
234    See the <a href=\"download.html\">Download</a> page for additional ways of
235    getting Guile.
236  </p>
237
238
239
240      </td>
241    </tr>
242  </table>
243
244  <br />
245  <div class=\"copyright\">
246
247    <p>
248      Please send FSF &amp; GNU inquiries &amp; questions to
249      <a href=\"mailto:gnu@gnu.org\"><em>gnu@gnu.org</em></a>. There are also
250      <a href=\"/home.html#ContactInfo\">other ways to contact</a> the FSF.
251    </p>
252
253    <p>
254      Please send comments on these web pages to
255      <a href=\"mailto:bug-guile@gnu.org\"><em>bug-guile@gnu.org</em></a>, send
256      other questions to <a href=\"mailto:gnu@gnu.org\"><em>gnu@gnu.org</em></a>.
257    </p>
258
259    <p>
260      Copyright (C) 2012  Free Software Foundation, Inc.
261    </p>
262
263    <p>
264      Verbatim copying and distribution of this entire web page is
265      permitted in any medium, provided this notice is preserved.<P>
266      Updated:
267
268      <!-- timestamp start -->
269      $Date: 2012/11/30 00:16:15 $ $Author: civodul $
270      <!-- timestamp end -->
271    </p>
272
273  </div>
274
275</body>
276</html>
277")
278
279(define head-request-headers:www.gnu.org/software/guile/
280  "HEAD /software/guile/ HTTP/1.1
281Host: www.gnu.org
282Connection: close
283
284")
285
286(define head-response-headers:www.gnu.org/software/guile/
287  "HTTP/1.1 200 OK
288Date: Fri, 11 Jan 2013 11:03:14 GMT
289Server: Apache/2.2.14
290Accept-Ranges: bytes
291Cache-Control: max-age=0
292Expires: Fri, 11 Jan 2013 11:03:14 GMT
293Vary: Accept-Encoding
294Content-Length: 8077
295Connection: close
296Content-Type: text/html
297Content-Language: en
298
299")
300
301;; Unfortunately, POST to http://www.gnu.org/software/guile/ succeeds!
302(define post-request-headers:www.apache.org/
303  "POST / HTTP/1.1
304Host: www.apache.org
305Connection: close
306
307")
308
309(define post-response-headers:www.apache.org/
310  "HTTP/1.1 405 Method Not Allowed
311Date: Fri, 11 Jan 2013 11:04:34 GMT
312Server: Apache/2.4.3 (Unix) OpenSSL/1.0.0g
313Allow: TRACE
314Content-Length: 314
315Connection: close
316Content-Type: text/html; charset=iso-8859-1
317
318")
319
320(define post-response-body:www.apache.org/
321"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
322<html><head>
323<title>405 Method Not Allowed</title>
324</head><body>
325<h1>Method Not Allowed</h1>
326<p>The requested method POST is not allowed for the URL /.</p>
327<hr>
328<address>Apache/2.4.3 (Unix) OpenSSL/1.0.0g Server at www.apache.org Port 80</address>
329</body></html>
330")
331
332(define put-request-headers:www.apache.org/
333  "PUT / HTTP/1.1
334Host: www.apache.org
335Connection: close
336
337")
338
339(define put-response-headers:www.apache.org/
340  "HTTP/1.1 405 Method Not Allowed
341Date: Fri, 11 Jan 2013 11:04:34 GMT
342Server: Apache/2.4.3 (Unix) OpenSSL/1.0.0g
343Allow: TRACE
344Content-Length: 313
345Connection: close
346Content-Type: text/html; charset=iso-8859-1
347
348")
349
350(define put-response-body:www.apache.org/
351  "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
352<html><head>
353<title>405 Method Not Allowed</title>
354</head><body>
355<h1>Method Not Allowed</h1>
356<p>The requested method PUT is not allowed for the URL /.</p>
357<hr>
358<address>Apache/2.4.3 (Unix) OpenSSL/1.0.0g Server at www.apache.org Port 80</address>
359</body></html>
360")
361
362(define delete-request-headers:www.apache.org/
363  "DELETE / HTTP/1.1
364Host: www.apache.org
365Connection: close
366
367")
368
369(define delete-response-headers:www.apache.org/
370  "HTTP/1.1 405 Method Not Allowed
371Date: Fri, 11 Jan 2013 11:07:19 GMT
372Server: Apache/2.4.3 (Unix) OpenSSL/1.0.0g
373Allow: TRACE
374Content-Length: 316
375Connection: close
376Content-Type: text/html; charset=iso-8859-1
377
378")
379
380
381
382(define delete-response-body:www.apache.org/
383  "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
384<html><head>
385<title>405 Method Not Allowed</title>
386</head><body>
387<h1>Method Not Allowed</h1>
388<p>The requested method DELETE is not allowed for the URL /.</p>
389<hr>
390<address>Apache/2.4.3 (Unix) OpenSSL/1.0.0g Server at www.apache.org Port 80</address>
391</body></html>
392")
393
394(define options-request-headers:www.apache.org/
395  "OPTIONS / HTTP/1.1
396Host: www.apache.org
397Connection: close
398
399")
400
401(define options-response-headers:www.apache.org/
402  "HTTP/1.1 200 OK
403Date: Fri, 11 Jan 2013 11:08:31 GMT
404Server: Apache/2.4.3 (Unix) OpenSSL/1.0.0g
405Allow: OPTIONS,GET,HEAD,POST,TRACE
406Cache-Control: max-age=3600
407Expires: Fri, 11 Jan 2013 12:08:31 GMT
408Content-Length: 0
409Connection: close
410Content-Type: text/html; charset=utf-8
411
412")
413
414;; This depends on the exact request that we send.  I copied this off
415;; the console with an "nc" session, so it doesn't include the CR bytes.
416;; But that's OK -- we just have to decode the body as an HTTP request
417;; and check that it's the same.
418(define trace-request-headers:www.apache.org/
419  "TRACE / HTTP/1.1\r
420Host: www.apache.org\r
421Connection: close\r
422\r
423")
424
425(define trace-response-headers:www.apache.org/
426  "HTTP/1.1 200 OK\r
427Date: Fri, 11 Jan 2013 12:36:13 GMT\r
428Server: Apache/2.4.3 (Unix) OpenSSL/1.0.0g\r
429Connection: close\r
430Transfer-Encoding: chunked\r
431Content-Type: message/http\r
432\r
433")
434
435(define trace-response-body:www.apache.org/
436  "3d\r
437TRACE / HTTP/1.1\r
438Host: www.apache.org\r
439Connection: close\r
440\r
441\r
4420\r
443\r
444")
445
446(define (requests-equal? r1 r2)
447  (and (equal? (request-method r1) (request-method r2))
448       (equal? (request-uri r1) (request-uri r2))
449       (equal? (request-version r1) (request-version r2))
450       (equal? (request-headers r1) (request-headers r2))))
451
452(define (responses-equal? r1 r2)
453  (and (equal? (response-code r1) (response-code r2))
454       (equal? (response-version r1) (response-version r2))
455       (equal? (response-headers r1) (response-headers r2))))
456
457(define* (run-with-http-transcript
458          expected-request expected-request-body request-body-encoding
459          response response-body response-body-encoding
460          proc)
461  (let ((reading? #f)
462        (writing? #t)
463        (response-port (open-input-string response))
464        (response-body-port (open-bytevector-input-port
465                             (string->bytevector response-body
466                                                 response-body-encoding))))
467    (call-with-values (lambda () (open-bytevector-output-port))
468      (lambda (request-port get-bytevector)
469        (define (put-char c)
470          (unless writing?
471            (error "Port closed for writing"))
472          (put-u8 request-port (char->integer c)))
473        (define (put-string s)
474          (string-for-each put-char s)
475          (set! writing? #f)
476          (set! reading? #t)
477          (let* ((p (open-bytevector-input-port (get-bytevector)))
478                 (actual-request (read-request p))
479                 (actual-body (read-request-body actual-request)))
480            (pass-if "requests equal"
481              (requests-equal? actual-request
482                               (call-with-input-string expected-request
483                                                       read-request)))
484            (pass-if "request bodies equal"
485              (equal? (or actual-body #vu8())
486                      (string->bytevector expected-request-body
487                                          request-body-encoding)))))
488        (define (get-char)
489          (unless reading?
490            (error "Port closed for reading"))
491          (let ((c (read-char response-port)))
492            (if (char? c)
493                c
494                (let ((u8 (get-u8 response-body-port)))
495                  (if (eof-object? u8)
496                      u8
497                      (integer->char u8))))))
498        (define (close)
499          (when writing?
500            (unless (eof-object? (get-u8 response-body-port))
501              (error "Failed to consume all of body"))))
502        (let ((soft-port (make-soft-port
503                          (vector put-char put-string #f get-char close)
504                          "rw")))
505          ;; Arrange it so that the only time our put-char/put-string
506          ;; functions are called is during force-output.
507          (setvbuf soft-port 'block 10000)
508          (proc soft-port))))))
509
510(define* (check-transaction method uri
511                            request-headers request-body request-body-encoding
512                            response-headers response-body response-body-encoding
513                            proc
514                            #:key (response-body-comparison response-body))
515  (with-test-prefix (string-append method " " uri)
516    (run-with-http-transcript
517     request-headers request-body request-body-encoding
518     response-headers response-body response-body-encoding
519     (lambda (port)
520       (call-with-values (lambda ()
521                           (proc uri #:port port))
522         (lambda (response body)
523           (pass-if "response equal"
524             (responses-equal?
525              response
526              (call-with-input-string response-headers read-response)))
527           (pass-if "response body equal"
528             (equal? (or body "") response-body-comparison))))))))
529
530(check-transaction
531 "GET" "http://www.gnu.org/software/guile/"
532 get-request-headers:www.gnu.org/software/guile/ "" "iso-8859-1"
533 get-response-headers:www.gnu.org/software/guile/
534 get-response-body:www.gnu.org/software/guile/ "iso-8859-1"
535 http-get)
536
537(check-transaction
538 "HEAD" "http://www.gnu.org/software/guile/"
539 head-request-headers:www.gnu.org/software/guile/ "" "iso-8859-1"
540 head-response-headers:www.gnu.org/software/guile/ "" "iso-8859-1"
541 http-head)
542
543(check-transaction
544 "POST" "http://www.apache.org/"
545 post-request-headers:www.apache.org/ "" "iso-8859-1"
546 post-response-headers:www.apache.org/
547 post-response-body:www.apache.org/ "iso-8859-1"
548 http-post)
549
550(check-transaction
551 "PUT" "http://www.apache.org/"
552 put-request-headers:www.apache.org/ "" "iso-8859-1"
553 put-response-headers:www.apache.org/
554 put-response-body:www.apache.org/ "iso-8859-1"
555 http-put)
556
557(check-transaction
558 "DELETE" "http://www.apache.org/"
559 delete-request-headers:www.apache.org/ "" "iso-8859-1"
560 delete-response-headers:www.apache.org/
561 delete-response-body:www.apache.org/ "iso-8859-1"
562 http-delete)
563
564(check-transaction
565 "OPTIONS" "http://www.apache.org/"
566 options-request-headers:www.apache.org/ "" "utf-8"
567 options-response-headers:www.apache.org/ "" "utf-8"
568 http-options)
569
570(check-transaction
571 "TRACE" "http://www.apache.org/"
572 trace-request-headers:www.apache.org/ "" "iso-8859-1"
573 trace-response-headers:www.apache.org/
574 trace-response-body:www.apache.org/ "iso-8859-1"
575 http-trace
576 #:response-body-comparison
577 ;; The body will be message/http, which is logically a sequence of
578 ;; bytes, not characters.  It happens that iso-8859-1 can encode our
579 ;; body and is compatible with the headers as well.
580 (string->bytevector trace-request-headers:www.apache.org/
581                     "iso-8859-1"))
582