Skip to content

Instantly share code, notes, and snippets.

@samth
Created May 31, 2012 15:40
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samth/2844243 to your computer and use it in GitHub Desktop.
Save samth/2844243 to your computer and use it in GitHub Desktop.
A simple http client in Racket by p4bl0
#lang racket/base
;; By http://www.reddit.com/user/p4bl0 at http://paste.fulltxt.net/FZ4-HeULc
;; `racket -il readline -t http-client.rkt`
;; then you can use (M "/path/...") where M can be get, post, put, delete.
;; When authenticated, you can logout using (logout).
;; The host and port can be changed using set!.
(require racket/tcp
racket/port
racket/string
racket/list
net/base64
net/uri-codec)
(provide (all-defined-out))
(define host "localhost")
(define port 8080)
(define name #f)
(define pass #f)
(define (logout)
(set! pass #f))
(define (make-auth-header)
(if (and name pass)
(string-append
"Authorization: Basic "
(bytes->string/utf-8
(base64-encode
(string->bytes/utf-8
(string-append name ":" pass)))))
""))
(define (parse-header header)
(let ((header (regexp-split ": " header)))
(cons (string->symbol (string-downcase (car header)))
(let ((v (string-join (cdr header) ": ")))
(if (string=? "" v)
v
(substring v 0 (sub1 (string-length v))))))))
(define (read-headers in)
(let loop ()
(let ((header (read-line in)))
(if (or (eof-object? header) (string=? header "\r"))
null
(list* (parse-header header) (loop))))))
(define (request method path [body ""])
(with-handlers
((exn:fail:network?
(lambda (e)
(display ";; Error: ")
(displayln (exn-message e)))))
(let-values (((in out) (tcp-connect host port)))
(file-stream-buffer-mode out 'line)
(display
(string-append (symbol->string method) " " path " HTTP/1.1\n"
"Host: " host "\n"
(make-auth-header)
"Content-length: "
(number->string (string-length body))
"\n\n"
(if (string=? "" body)
""
(string-append body "\n\n")))
out)
(close-output-port out)
(displayln ";; Request sent.")
(let ((evt (read-line-evt in)))
(let ((status (sync evt))
(headers (read-headers in)))
(let ((code (string->number (substring status 9 12))))
(case code
((401)
(let* ((auth-hdr (assoc 'www-authenticate headers))
(realm (substring (cdr auth-hdr)
12 (string-length (cdr auth-hdr)))))
(display ";; Authentication required: ")
(displayln realm)
(display "; Username: ")
(set! name (read-line))
(unless (string=? "" name)
(display "; Password: ")
(set! pass (read-line))
(request method path body))))
((303)
(let* ((url (assoc 'location headers))
(path (cadr (regexp-split
(string-append
host ":" (number->string port))
(cdr url)))))
(display ";; Redirection: ")
(displayln (cdr url))
(display "; Follow? [y/N]: ")
(let ((f (read-line)))
(when (string=? "y" f)
(request 'GET path)))))
(else
(display ";; Response (")
(display (number->string code))
(displayln "):")
(let loop ()
(let ((s (read-line in)))
(unless (eof-object? s)
(displayln s)
(loop))))
(displayln ";; End of response.")))
(close-input-port in)))))))
(define (make-body)
(let ((b (let loop ((body ""))
(display "; Add binding: ")
(let ((arg-name (read-line)))
(if (string=? "" arg-name)
body
(begin
(display "; Value: ")
(let ((arg-value (read-line)))
(loop
(string-append
body
(string-append arg-name "="
(uri-encode arg-value) "&"))))))))))
(substring b 0 (sub1 (string-length b)))))
(define (get path)
(read-line)
(request 'GET path))
(define (post path)
(read-line)
(request 'POST path (make-body)))
(define (put path)
(read-line)
(request 'PUT path (make-body)))
(define (delete path)
(read-line)
(request 'DELETE path))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment