Browse code
Initial
fiddlerwoaroof authored on 31/05/2017 23:33:27
Showing 3 changed files
Showing 3 changed files
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,13 @@ |
1 |
+;;;; http-streams.asd |
|
2 |
+ |
|
3 |
+(asdf:defsystem #:http-streams |
|
4 |
+ :description "Describe http-streams here" |
|
5 |
+ :author "Your Name <your.name@example.com>" |
|
6 |
+ :license "Specify license here" |
|
7 |
+ :depends-on (#:fwoar.lisputils |
|
8 |
+ #:alexandria |
|
9 |
+ #:serapeum) |
|
10 |
+ :serial t |
|
11 |
+ :components ((:file "package") |
|
12 |
+ (:file "http-streams"))) |
|
13 |
+ |
0 | 14 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,154 @@ |
1 |
+;;;; http-streams.lisp |
|
2 |
+ |
|
3 |
+(in-package #:http-streams) |
|
4 |
+ |
|
5 |
+;;; "http-streams" goes here. Hacks and glory await! |
|
6 |
+ |
|
7 |
+(define-condition no-pipe (error) |
|
8 |
+ ()) |
|
9 |
+ |
|
10 |
+(defclass piped-object () |
|
11 |
+ ((%pipe :reader pipe :initarg :pipe :initform (error 'no-pipe)))) |
|
12 |
+ |
|
13 |
+(defun end-line (stream) |
|
14 |
+ (princ #. (coerce '(#\return #\newline) 'string) stream)) |
|
15 |
+ |
|
16 |
+(defmacro with-line ((stream) form) |
|
17 |
+ `(prog1 ,form |
|
18 |
+ (end-line ,stream))) |
|
19 |
+ |
|
20 |
+(defun request (method path out-stream) |
|
21 |
+ (with-line (out-stream) |
|
22 |
+ (format out-stream "~a ~a HTTP/1.1" method path)) |
|
23 |
+ out-stream) |
|
24 |
+ |
|
25 |
+(defun header (name value out-stream) |
|
26 |
+ (with-line (out-stream) |
|
27 |
+ (format out-stream "~a: ~a" name value)) |
|
28 |
+ out-stream) |
|
29 |
+ |
|
30 |
+(defun body (value out-stream) |
|
31 |
+ (end-line out-stream) |
|
32 |
+ (etypecase value |
|
33 |
+ (stream (loop with buffer = (make-string 1000) |
|
34 |
+ for end-read = (read-sequence buffer value) |
|
35 |
+ while (/= end-read 0) |
|
36 |
+ do (write-sequence buffer out-stream :end end-read))) |
|
37 |
+ (string (write-sequence value out-stream)))) |
|
38 |
+ |
|
39 |
+(defmacro define-header-function (name header) |
|
40 |
+ (alexandria:once-only (header) |
|
41 |
+ `(defun ,name (value out-stream) |
|
42 |
+ (header ,header value out-stream)))) |
|
43 |
+ |
|
44 |
+(defmacro define-header-functions (() &body definitions) |
|
45 |
+ `(progn ,@(mapcar (fw.lu:destructuring-lambda ((name header)) |
|
46 |
+ `(define-header-function ,name ,header)) |
|
47 |
+ definitions))) |
|
48 |
+ |
|
49 |
+(define-header-functions () |
|
50 |
+ (content-type "Content-Type") |
|
51 |
+ (host "Host") |
|
52 |
+ (accept "Accept") |
|
53 |
+ (pragma "Pragma") |
|
54 |
+ (user-agent "User-Agent") |
|
55 |
+ (cache-control "Cache-Control") |
|
56 |
+ (accept-encoding "Accept-Encoding") |
|
57 |
+ (accept-language "Accept-Language")) |
|
58 |
+ |
|
59 |
+(defmacro /> (name (&rest args) &body funs) |
|
60 |
+ (alexandria:with-gensyms (out) |
|
61 |
+ `(defun ,name (,@args ,out) |
|
62 |
+ ,@(mapcar (serapeum:op `(,@_ ,out)) |
|
63 |
+ funs)))) |
|
64 |
+ |
|
65 |
+(/> build-subreddit-request (method subreddit) |
|
66 |
+ (request method (format nil "/r/~a.rss" subreddit)) |
|
67 |
+ (accept "text/html") |
|
68 |
+ (accept-language "en-US,en;q=0.8") |
|
69 |
+ (cache-control "no-cache") |
|
70 |
+ (host "www.reddit.com") |
|
71 |
+ (pragma "no-cache") |
|
72 |
+ (header "Upgrade-Insecure-Requests" "1") |
|
73 |
+ (user-agent "foobar 0.0") |
|
74 |
+ (body "")) |
|
75 |
+ |
|
76 |
+(defun read-in-header (stream) |
|
77 |
+ (values (read-line stream) |
|
78 |
+ (loop with headers = () |
|
79 |
+ for line = (read-line stream nil) |
|
80 |
+ while line |
|
81 |
+ until (string= line " ") |
|
82 |
+ do |
|
83 |
+ (push (map 'list 'metatilities:strip-whitespace |
|
84 |
+ (fwoar.string-utils:split #\: (metatilities:strip-whitespace line) :count 2)) |
|
85 |
+ headers) |
|
86 |
+ finally |
|
87 |
+ (return (nreverse headers))))) |
|
88 |
+ |
|
89 |
+(defmacro with-subreddit-request ((subreddit stream) &body body) |
|
90 |
+ (alexandria:with-gensyms (socket sock-stream ssl-stream) |
|
91 |
+ `(usocket:with-client-socket (,socket ,sock-stream "reddit.com" 443 :element-type '(unsigned-byte 8)) |
|
92 |
+ (let* ((,ssl-stream (cl+ssl:make-ssl-client-stream ,sock-stream :hostname "reddit.com")) |
|
93 |
+ (,stream (flexi-streams:make-flexi-stream ,ssl-stream :external-format :iso-8859-1))) |
|
94 |
+ (build-subreddit-request :GET ,subreddit ,stream) |
|
95 |
+ (finish-output ,stream) |
|
96 |
+ (unwind-protect (progn ,@body) |
|
97 |
+ (close ,ssl-stream)))))) |
|
98 |
+ |
|
99 |
+(defun get-header (name headers) |
|
100 |
+ (cadr (assoc name headers :test 'equalp))) |
|
101 |
+ |
|
102 |
+(defun get-content-length (headers) |
|
103 |
+ (parse-integer (get-header "Content-Length" headers))) |
|
104 |
+ |
|
105 |
+(defmacro with-parsed-response (char-stream (result headers reply-buf content-length) &body body) |
|
106 |
+ (alexandria:once-only (char-stream) |
|
107 |
+ `(multiple-value-bind (,result ,headers) (read-in-header ,char-stream) |
|
108 |
+ (let* ((,reply-buf (make-string (get-content-length ,headers))) |
|
109 |
+ (,content-length (read-sequence ,reply-buf ,char-stream))) |
|
110 |
+ ,@body)))) |
|
111 |
+ |
|
112 |
+(defun get-subreddit-feed (&optional (subreddit "lisp")) |
|
113 |
+ (with-subreddit-request (subreddit char-stream) |
|
114 |
+ (with-parsed-response char-stream (result headers reply-buf content-length) |
|
115 |
+ (values reply-buf |
|
116 |
+ result |
|
117 |
+ headers |
|
118 |
+ content-length)))) |
|
119 |
+ |
|
120 |
+ |
|
121 |
+#| |
|
122 |
+(defun get-subreddit-feed () |
|
123 |
+ (usocket:with-client-socket (so st "reddit.com" 443 :element-type '(unsigned-byte 8)) |
|
124 |
+ (let* ((ssl-stream (cl+ssl:make-ssl-client-stream st :hostname "reddit.com")) |
|
125 |
+ (char-stream (flexi-streams:make-flexi-stream ssl-stream :external-format :iso-8859-1))) |
|
126 |
+ (unwind-protect (locally (declare (optimize (debug 3))) |
|
127 |
+ (progn (build-subreddit-request :GET "lisp" char-stream) |
|
128 |
+ (finish-output char-stream) |
|
129 |
+ (multiple-value-bind (result headers) (read-in-header char-stream) |
|
130 |
+ (let ((reply-buf (make-string (parse-integer (cadr (assoc "Content-Length" headers |
|
131 |
+ :test 'equalp)))))) |
|
132 |
+ (values reply-buf |
|
133 |
+ result |
|
134 |
+ headers |
|
135 |
+ (read-sequence reply-buf char-stream)))))) |
|
136 |
+ (close ssl-stream))))) |
|
137 |
+ |
|
138 |
+(defparameter *old-readtable* (copy-readtable *readtable*)) |
|
139 |
+(set-macro-character #\] 'identity nil) |
|
140 |
+(set-dispatch-macro-character |
|
141 |
+ #\# #\[ |
|
142 |
+ (lambda (s c n) |
|
143 |
+ (let ((list (read-delimited-list #\] s t))) |
|
144 |
+ (fw.lu:rollup-list |
|
145 |
+ (loop with result = () |
|
146 |
+ for (arg op) on list by #'cddr |
|
147 |
+ while op |
|
148 |
+ do |
|
149 |
+ (push `(,op ,arg) |
|
150 |
+ result) |
|
151 |
+ finally |
|
152 |
+ (push arg result) |
|
153 |
+ (return (nreverse result))))))) |
|
154 |
+|# |