2 ;;;; Copyright (c) 2008 Zachary Beane, All Rights Reserved
4 ;;;; Redistribution and use in source and binary forms, with or without
5 ;;;; modification, are permitted provided that the following conditions
8 ;;;; * Redistributions of source code must retain the above copyright
9 ;;;; notice, this list of conditions and the following disclaimer.
11 ;;;; * Redistributions in binary form must reproduce the above
12 ;;;; copyright notice, this list of conditions and the following
13 ;;;; disclaimer in the documentation and/or other materials
14 ;;;; provided with the distribution.
16 ;;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
17 ;;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 ;;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ;;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 ;;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22 ;;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 ;;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 ;;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 ;;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 ;;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 (defvar *response-element-classes
*
33 (make-hash-table :test
'equal
))
35 (defun set-element-class (element-name class
)
36 (setf (gethash element-name
*response-element-classes
*) class
))
50 :accessor http-phrase
)
52 :initarg
:http-headers
53 :accessor http-headers
))
58 :http-phrase
"<uninitialized>"
62 (defmethod print-object ((response response
) stream
)
63 (print-unreadable-object (response stream
:type t
:identity t
)
64 (format stream
"~D ~S" (http-code response
) (http-phrase response
))))
66 (defgeneric xml-string
(response)
68 (flexi-streams:octets-to-string
(body response
) :external-format
:utf-8
)))
70 (defgeneric response-specialized-class
(name)
72 (gethash name
*response-element-classes
*)))
74 (defgeneric specialized-initialize
(object source
)
75 (:method
(object source
)
78 (defgeneric content-length
(response)
80 (parse-integer (bvalue :content-length
(http-headers response
)))))
82 (defgeneric specialize-response
(response)
83 (:method
((response response
))
84 (cond ((or (null (body response
))
85 (and (not (streamp (body response
)))
86 (zerop (length (body response
)))))
89 (let* ((source (xml-source (body response
)))
90 (type (xml-document-element source
))
91 (class (response-specialized-class type
)))
93 (change-class response class
)
94 (specialized-initialize response source
))
97 (defun request-response (request &key
100 (handler 'specialize-response
))
101 (setf (endpoint request
) (redirected-endpoint (endpoint request
)
103 (multiple-value-bind (body code headers uri stream must-close phrase
)
104 (send request
:want-stream body-stream
)
105 (declare (ignore uri must-close
))
107 (make-instance 'response
112 :http-headers headers
)))
114 (funcall handler response
)
115 (with-open-stream (stream stream
)
116 (funcall handler response
))))))
118 (defun submit-request (request
119 &key body-stream keep-stream
120 (handler 'specialize-response
))
123 (let ((response (request-response request
124 :keep-stream keep-stream
125 :body-stream body-stream
127 (maybe-signal-error response
)
128 (setf (request response
) request
)
130 (temporary-redirect (condition)
131 (setf (endpoint request
)
132 (request-error-endpoint condition
)))
133 (permanent-redirect (condition)
134 ;; Remember the new endpoint long-term
135 (let ((new-endpoint (request-error-endpoint condition
)))
136 (setf (redirected-endpoint (endpoint request
)
139 (setf (endpoint request
) new-endpoint
)))
141 ;; Per the S3 docs, InternalErrors should simply be retried