Mercurial > core / lisp/lib/net/req.lisp
changeset 359: |
0e00dec3de03 |
child: |
5b6a2a8ba83e |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Wed, 22 May 2024 22:16:26 -0400 |
permissions: |
-rw-r--r-- |
description: |
macs/control macros, seq functions, ported cl-cookie, added uri/domain.lisp, fully ported http! next we should remove dependence on cl+ssl |
1 ;;; net/req.lisp --- HTTP Request API 3 ;; based on Fukamachi's DEXADOR 9 (define-condition http-request-failed (error) 11 :reader response-body) 12 (status :initarg :status 13 :reader response-status) 14 (headers :initarg :headers 15 :reader response-headers) 18 (method :initarg :method 19 :reader request-method)) 20 (:report (lambda (condition stream) 21 (with-slots (uri status) condition 22 (format stream "An HTTP request to ~S has failed (status=~D)." 26 (defmacro define-request-failed-condition (name code) 27 `(define-condition ,(intern (format nil "~A-~A" :http-request name)) (http-request-failed) 29 (:report (lambda (condition stream) 30 (with-slots (body uri) condition 31 (format stream ,(format nil "An HTTP request to ~~S returned ~D ~A.~~2%~~A" 33 (substitute #\Space #\- (string-downcase name))) 38 (defvar *request-failed-error* (make-hash-table :test 'eql)) 41 ,@(loop for (name . code) in '(;; 4xx (Client Errors) 44 (payment-required . 402) 47 (method-not-allowed . 405) 48 (not-acceptable . 406) 49 (proxy-authentication-required . 407) 50 (request-timeout . 408) 53 (length-required . 411) 54 (precondition-failed . 412) 55 (payload-too-large . 413) 57 (unsupported-media-type . 415) 58 (range-not-satisfiable . 416) 59 (expectation-failed . 417) 60 (misdirected-request . 421) 61 (upgrade-required . 426) 62 (too-many-requests . 429) 64 ;; 5xx (Server Errors) 65 (internal-server-error . 500) 66 (not-implemented . 501) 68 (service-unavailable . 503) 69 (gateway-timeout . 504) 70 (http-version-not-supported . 505)) 71 collect `(define-request-failed-condition ,name ,code) 72 collect `(setf (gethash ,code *request-failed-error*) 73 ',(intern (format nil "~A-~A" :http-request name))))) 75 (defun http-request-failed (status &key body headers uri method) 78 (gethash status *request-failed-error* 'http-request-failed) 85 (define-condition socks5-proxy-request-failed (http-request-failed) 86 ((reason :initarg :reason)) 87 (:report (lambda (condition stream) 88 (with-slots (uri reason) condition 89 (format stream "An HTTP request to ~S via SOCKS5 has failed (reason=~S)." 94 (defvar *default-connect-timeout* 10) 95 (defvar *default-read-timeout* 10) 96 (defvar *verbose* nil) 99 (defvar *default-proxy* (or #-windows (uiop:getenv "HTTPS_PROXY") 100 #-windows (uiop:getenv "HTTP_PROXY")) 101 "If specified will be used as the default value of PROXY in calls to dexador. Defaults to 102 the value of the environment variable HTTPS_PROXY or HTTP_PROXY if not on Windows.") 104 (define-constant +crlf+ (string-to-octets (format nil "~C~C" #\Return #\Newline)) :test 'equalp) 107 (defparameter *default-user-agent* 108 (format nil "cc/req (~A~@[ ~A~]); ~A;~@[ ~A~]" 109 (lisp-implementation-type) 110 (lisp-implementation-version) 112 (software-version)))) 114 (defparameter *header-buffer* nil) 116 (defun write-first-line (method uri version &optional (buffer *header-buffer*)) 117 (fast-write-sequence (string-to-octets (string method)) buffer) 118 (fast-write-byte #.(char-code #\Space) buffer) 119 (fast-write-sequence (string-to-octets 120 (format nil "~A~:[~;~:*?~A~]" 121 (or (uri-path uri) "/") 124 (fast-write-byte #.(char-code #\Space) buffer) 125 (fast-write-sequence (ecase version 126 (1.1 (string-to-octets "HTTP/1.1")) 127 (1.0 (string-to-octets "HTTP/1.0"))) 129 (fast-write-sequence +crlf+ buffer)) 131 (defun write-header-field (name buffer) 132 (fast-write-sequence (if (typep name 'octet-vector) 134 (string-to-octets (string-capitalize name))) 137 (defun write-header-value (value buffer) 138 (fast-write-sequence (if (typep value 'octet-vector) 140 (string-to-octets (princ-to-string value))) 143 (defun write-header (name value &optional (buffer *header-buffer*)) 144 (write-header-field name buffer) 145 (fast-write-sequence (string-to-octets ": ") buffer) 146 (write-header-value value buffer) 147 (fast-write-sequence +crlf+ buffer)) 149 (define-compiler-macro write-header (name value &optional (buffer '*header-buffer*)) 151 ,(if (and (constantp name) 152 (typep name '(or keyword string))) 153 `(fast-write-sequence (string-to-octets ,(string-capitalize name)) ,buffer) 154 `(write-header-field ,name ,buffer)) 155 (fast-write-sequence (string-to-octets ": ") ,buffer) 156 ,(if (constantp value) 157 `(fast-write-sequence (string-to-octets ,(string value)) ,buffer) 158 `(write-header-value ,value ,buffer)) 159 (fast-write-sequence +crlf+ ,buffer))) 161 (defmacro with-header-output ((buffer &optional output) &body body) 162 `(fast-io:with-fast-output (,buffer ,output) 163 (declare (ignorable ,buffer)) 164 (let ((*header-buffer* ,buffer)) 167 (defun write-connect-header (uri version buffer &optional proxy-auth) 168 (fast-write-sequence (string-to-octets "CONNECT") buffer) 169 (fast-write-byte #.(char-code #\Space) buffer) 170 (fast-write-sequence (string-to-octets (format nil "~A:~A" 174 (fast-write-byte #.(char-code #\Space) buffer) 175 (fast-write-sequence (ecase version 176 (1.1 (string-to-octets "HTTP/1.1")) 177 (1.0 (string-to-octets "HTTP/1.0"))) 179 (fast-write-sequence +crlf+ buffer) 180 (fast-write-sequence (string-to-octets "Host:") buffer) 181 (fast-write-byte #.(char-code #\Space) buffer) 182 (fast-write-sequence (string-to-octets (format nil "~A:~A" 187 (fast-write-sequence +crlf+ buffer) 188 (fast-write-sequence (string-to-octets "Proxy-Authorization:") buffer) 189 (fast-write-byte #.(char-code #\Space) buffer) 190 (fast-write-sequence (string-to-octets proxy-auth) buffer)) 191 (fast-write-sequence +crlf+ buffer) 192 (fast-write-sequence +crlf+ buffer)) 194 (defun make-random-string (&optional (length 12)) 195 (declare (type fixnum length)) 196 (let ((result (make-string length))) 197 (declare (type simple-string result)) 198 (dotimes (i length result) 199 (setf (aref result i) 201 ((0 1) (code-char (+ #.(char-code #\a) (random 26)))) 202 ((2 3) (code-char (+ #.(char-code #\A) (random 26)))) 203 ((4) (code-char (+ #.(char-code #\0) (random 10))))))))) 206 (defun parse-content-type (content-type) 209 (ppcre:scan-to-strings "^\\s*?(\\w+)/([^;\\s]+)(?:\\s*;\\s*charset=([A-Za-z0-9_-]+))?" 212 (values (aref types 0) 216 (defun charset-to-encoding (charset &optional 217 (default *default-external-format*)) 221 ((string-equal charset "utf-8") 223 ((string-equal charset "euc-jp") 225 ((or (string-equal charset "shift_jis") 226 (string-equal charset "shift-jis")) 228 ((string-equal charset "windows-31j") 230 (t (or (when (sb-impl::get-external-format (keywordicate charset)) charset) 233 (defun detect-charset (content-type body) 234 (multiple-value-bind (type subtype charset) 235 (parse-content-type content-type) 237 ((charset-to-encoding charset nil)) 238 ((string-equal type "text") 239 (or (charset-to-encoding charset nil) 240 (if (and (string-equal subtype "html") 241 (typep body '(array (unsigned-byte 8) (*)))) 242 (charset-to-encoding (detect-charset-from-html body) nil) 245 ((and (string-equal type "application") 246 (or (string-equal subtype "json") 247 (string-equal subtype "javascript"))) 248 ;; According to RFC4627 (http://www.ietf.org/rfc/rfc4627.txt), 249 ;; JSON text SHALL be encoded in Unicode. The default encoding is UTF-8. 250 ;; It's possible to determine if the encoding is UTF-16 or UTF-36 251 ;; by looking at the first four octets, however, I leave it to the future. 253 ;; According to RFC4329 (https://datatracker.ietf.org/doc/html/rfc4329), 254 ;; javascript also is specified by charset, or defaults to UTF-8 255 ;; It's also possible to specify in the first four octets, but 256 ;; like application/json I leave it to the future. 257 (charset-to-encoding charset :utf-8)) 258 ((and (string-equal type "application") 259 (ppcre:scan "(?:[^+]+\\+)?xml" subtype)) 260 (charset-to-encoding charset))))) 262 (defun detect-charset-from-html (body) 263 "Detect the body's charset by (roughly) searching meta tags which has \"charset\" attribute." 264 (labels ((find-meta (start) 265 (search #.(string-to-octets "<meta ") body :start2 start)) 267 (let ((start (find-meta start))) 269 (return-from main nil)) 270 (let ((end (position (char-code #\>) body :start start :test #'=))) 272 (return-from main nil)) 274 (let ((match (nth-value 1 (ppcre:scan-to-strings 275 "charset=[\"']?([^\\s\"'>]+)[\"']?" 276 (octets-to-string body :start start :end end))))) 282 ;;; keep-alive-stream 283 (defclass keep-alive-stream (fundamental-input-stream) 284 ((stream :type (or null stream) 286 :initform (error ":stream is required") 287 :accessor keep-alive-stream-stream 288 :documentation "A stream; when we read END elements from it, we call CLOSE-ACTION on it and 289 set this slot to nil.") 292 :accessor keep-alive-stream-end) 293 (close-action :initarg :on-close-or-eof :reader close-action 294 :documentation "A (lambda (stream abort)) which will be called with keep-alive-stream-stream 295 when the stream is either closed or we hit end of file or we hit end"))) 297 (defun keep-alive-stream-close-underlying-stream (underlying-stream abort) 298 (when (and underlying-stream (open-stream-p underlying-stream)) 299 (close underlying-stream :abort abort))) 301 (defclass keep-alive-chunked-stream (keep-alive-stream) 302 ((chunga-stream :initarg :chunga-stream :accessor chunga-stream))) 304 (defun make-keep-alive-stream (stream &key end chunked-stream (on-close-or-eof #'keep-alive-stream-close-underlying-stream)) 305 "ON-CLOSE-OR-EOF takes a single parameter, STREAM (the stream passed in here, not the 306 keep-alive-stream), and should handle clean-up of it" 307 (assert (xor end chunked-stream)) 309 (make-instance 'keep-alive-chunked-stream :stream stream :chunga-stream chunked-stream :on-close-or-eof on-close-or-eof) 310 (make-instance 'keep-alive-stream :stream stream :end end :on-close-or-eof on-close-or-eof))) 312 (defun maybe-close (stream &optional (close-if nil)) 313 "Will close the underlying stream if close-if is T (unless it is already closed). 314 If the stream is already closed or we closed it returns :EOF otherwise NIL." 315 (let ((underlying-stream (keep-alive-stream-stream stream))) 317 ((not underlying-stream) 320 (funcall (close-action stream) underlying-stream nil) 321 (setf (keep-alive-stream-stream stream) nil) 325 (defmethod stream-read-byte ((stream keep-alive-stream)) 326 "Return :EOF or byte read. When we hit EOF or finish reading our allowed content, 327 call the close-action on our underlying-stream and return EOF." 329 (underlying-stream (keep-alive-stream-stream stream))) 330 (or (maybe-close stream (<= (keep-alive-stream-end stream) 0)) 332 (setf byte (read-byte underlying-stream nil :eof)) 333 (decf (keep-alive-stream-end stream) 1) 334 (maybe-close stream (or (<= (keep-alive-stream-end stream) 0) (eql byte :eof))) 337 (defmethod stream-read-byte ((stream keep-alive-chunked-stream)) 338 "Return :EOF or byte read. When we hit :EOF or finish reading our chunk, 339 call the close-action on our underlying-stream and return :EOF" 340 (or (maybe-close stream) 341 (if (chunga:chunked-stream-input-chunking-p (chunga-stream stream)) 342 (let ((byte (read-byte (chunga-stream stream) nil :eof))) 346 (maybe-close stream t)) 348 (or (maybe-close stream t) :eof)))) 350 (defmethod stream-read-sequence ((stream keep-alive-stream) sequence start end &key) 351 (declare (optimize speed)) 352 (if (null (keep-alive-stream-stream stream)) ;; we already closed it 354 (let* ((to-read (min (- end start) (keep-alive-stream-end stream))) 355 (n (read-sequence sequence (keep-alive-stream-stream stream) 357 :end (+ start to-read)))) 358 (decf (keep-alive-stream-end stream) (- n start)) 359 (maybe-close stream (<= (keep-alive-stream-end stream) 0)) 362 (defmethod stream-read-sequence ((stream keep-alive-chunked-stream) sequence start end &key) 363 (declare (optimize speed)) 364 (if (null (keep-alive-stream-stream stream)) ;; we already closed it 366 (if (chunga:chunked-stream-input-chunking-p (chunga-stream stream)) 368 (let ((num-read (read-sequence sequence (chunga-stream stream) :start start :end end))) 370 (maybe-close stream (not (chunga:chunked-stream-input-chunking-p (chunga-stream stream))))) 373 (defmethod stream-element-type ((stream keep-alive-chunked-stream)) 374 (stream-element-type (chunga-stream stream))) 376 (defmethod stream-element-type ((stream keep-alive-stream)) 379 (defmethod open-stream-p ((stream keep-alive-stream)) 380 (let ((underlying-stream (keep-alive-stream-stream stream))) 381 (and underlying-stream (open-stream-p underlying-stream)))) 383 (defmethod close ((stream keep-alive-stream) &key abort) 384 (funcall (close-action stream) (keep-alive-stream-stream stream) abort) 385 (setf (keep-alive-stream-stream stream) nil)) 388 (declaim (type fixnum +buffer-size+)) 389 (eval-when (:compile-toplevel :load-toplevel :execute) 390 (defconstant +buffer-size+ 128)) 392 (defclass decoding-stream (fundamental-character-input-stream) 393 ((stream :type stream 395 :initform (error ":stream is required") 396 :accessor decoding-stream-stream) 397 (encoding :initarg :encoding 398 :initform (error ":encoding is required") 399 :accessor decoding-stream-encoding) 400 (buffer :type (simple-array (unsigned-byte 8) (#.+buffer-size+)) 401 :initform (make-array +buffer-size+ :element-type '(unsigned-byte 8)) 402 :accessor decoding-stream-buffer) 403 (buffer-position :type fixnum 404 :initform +buffer-size+ 405 :accessor decoding-stream-buffer-position) 406 (buffer-end-position :type fixnum 408 :accessor decoding-stream-buffer-end-position) 409 (last-char :type character 411 :accessor decoding-stream-last-char) 412 (last-char-size :type fixnum 414 :accessor decoding-stream-last-char-size) 415 (on-close :type (or null function) :initform nil :initarg :on-close))) 417 (defmethod initialize-instance :after ((stream decoding-stream) &rest initargs) 418 (declare (ignore initargs)) 419 (with-slots (encoding) stream 420 (when (keywordp encoding) 421 (setf encoding (babel-encodings:get-character-encoding encoding))))) 423 (defun make-decoding-stream (stream &key (encoding babel-encodings:*default-character-encoding*) 425 (let ((decoding-stream (make-instance 'decoding-stream 428 :on-close on-close))) 429 (fill-buffer decoding-stream) 432 (defun fill-buffer (stream) 433 (declare (optimize speed)) 434 (with-slots (stream buffer buffer-position buffer-end-position) stream 435 (declare (type (simple-array (unsigned-byte 8) (#.+buffer-size+)) buffer) 436 (type fixnum buffer-position)) 437 (let ((to-read (- +buffer-size+ buffer-position))) 438 (declare (type fixnum to-read)) 439 (replace buffer buffer 441 :start2 buffer-position 443 (setf buffer-position 0) 444 (let ((n (read-sequence buffer stream :start to-read))) 445 (declare (type fixnum n)) 446 (unless (= n +buffer-size+) 447 (setf buffer-end-position n)))))) 449 (defun needs-to-fill-buffer-p (stream) 450 (declare (optimize speed)) 451 (when (/= -1 (the fixnum (decoding-stream-buffer-end-position stream))) 452 (return-from needs-to-fill-buffer-p nil)) 454 (with-slots (buffer-position encoding) stream 455 (< (- +buffer-size+ (the fixnum buffer-position)) 456 (the fixnum (babel-encodings:enc-max-units-per-char encoding))))) 459 (defmethod stream-read-char ((stream decoding-stream)) 460 (declare (optimize speed)) 461 (when (needs-to-fill-buffer-p stream) 462 (fill-buffer stream)) 464 (when (= (the fixnum (decoding-stream-buffer-end-position stream)) 465 (the fixnum (decoding-stream-buffer-position stream))) 466 (return-from stream-read-char :eof)) 468 (with-slots (buffer buffer-position encoding last-char last-char-size) 470 (declare (fixnum buffer-position)) 471 (let* ((mapping (babel-encodings:lookup-mapping babel::*string-vector-mappings* encoding)) 472 (counter (babel-encodings:code-point-counter mapping))) 473 (declare (type function counter)) 474 (multiple-value-bind (chars new-end) 475 (funcall counter buffer buffer-position +buffer-size+ 1) 476 (declare (ignore chars) (fixnum new-end)) 477 (let ((string (make-string 1 :element-type 'babel:unicode-char)) 478 (size (the fixnum (- new-end buffer-position)))) 479 (funcall (the function (babel-encodings:decoder mapping)) 480 buffer buffer-position new-end string 0) 481 (setf buffer-position new-end 482 last-char (aref string 0) 486 (defmethod stream-unread-char ((stream decoding-stream) char) 487 (let ((last-char (decoding-stream-last-char stream))) 488 (when (char= last-char #\Nul) 489 (error "No character to unread from this stream")) 490 (unless (char= char last-char) 491 (error "Last character read (~S) was different from ~S" 493 (with-slots (buffer-position last-char-size) stream 494 (decf buffer-position last-char-size)) 495 (with-slots (last-char last-char-size) stream 496 (setf last-char #\Nul 500 #+(or abcl clasp ecl) 501 (defmethod stream-read-sequence ((stream decoding-stream) sequence start end &key) 502 (loop for i from start to end 503 for char = (stream-read-char stream) 506 else do (setf (aref sequence i) char) 507 finally (return end))) 510 (defmethod stream-read-byte ((stream decoding-stream)) 511 (with-slots (last-char last-char-size) stream 512 (setf last-char #\Nul 514 (read-byte (decoding-stream-stream stream) nil :eof)) 516 (defmethod open-stream-p ((stream decoding-stream)) 517 (open-stream-p (decoding-stream-stream stream))) 519 (defmethod stream-element-type ((stream decoding-stream)) 522 (defmethod close ((stream decoding-stream) &key abort) 523 ;; TODO: modify me to return the connection to the connection pool 524 (with-slots (stream) stream 525 (when (open-stream-p stream) 526 (close stream :abort abort)))) 529 (defun decode-body (content-type body &key default-charset on-close) 530 (let ((charset (or (and content-type 531 (detect-charset content-type body)) 533 (babel-encodings:*suppress-character-coding-errors* t)) 537 (make-decoding-stream body :encoding charset :on-close on-close) 538 (babel:octets-to-string body :encoding (keywordicate charset))) 539 (babel:character-decoding-error (e) 540 (warn (format nil "Failed to decode the body to ~S due to the following error (falling back to binary):~% ~A" 543 (return-from decode-body body))) 546 (defun content-disposition (key val) 548 (cons (content-disposition key (first val))) 550 (let* ((filename (file-namestring val)) 551 (utf8-filename-p (find-if (lambda (char) 552 (< 127 (char-code char))) 554 (format nil "Content-Disposition: form-data; name=\"~A\"; ~:[filename=\"~A\"~;filename*=UTF-8''~A~]~C~C" 558 (obj/uri:parse-uri filename) 560 #\Return #\Newline))) 562 (format nil "Content-Disposition: form-data; name=\"~A\"~C~C" 564 #\Return #\Newline)))) 566 (defmacro define-alist-cache (cache-name) 567 (let ((var (intern (format nil "*~A*" cache-name)))) 570 (defun ,(intern (format nil "LOOKUP-IN-~A" cache-name)) (elt) 572 (alexandria:assoc-value ,var elt))) 573 (defun (setf ,(intern (format nil "LOOKUP-IN-~A" cache-name))) (val elt) 575 (setf (alexandria:assoc-value ,var elt) val)) 578 ;; If bound, an alist mapping content to content-type, 579 ;; used to avoid determining content type multiple times 580 (define-alist-cache content-type-cache) 581 ;; If bound, an alist mapping content to encoded content, to avoid 582 ;; double converting content when we must calculate its length first 583 (define-alist-cache content-encoding-cache) 585 (defmacro with-content-caches (&body body) 586 `(let ((*content-type-cache* nil) 587 (*content-encoding-cache* nil)) 590 (defun content-type (value) 592 (pathname (or (lookup-in-content-type-cache value) 593 (setf (lookup-in-content-type-cache value) (mimes:mime value)))) 596 (defun multipart-value-content-type (value) 599 (destructuring-bind (val &key content-type) 601 (or content-type (content-type val)))) 602 (otherwise (content-type value)))) 604 (defun convert-to-octets (val) 605 (or (lookup-in-content-encoding-cache val) 606 (setf (lookup-in-content-encoding-cache val) 608 (string (babel:string-to-octets val)) 609 ((array (unsigned-byte 8) (*)) val) 610 (symbol (babel:string-to-octets (princ-to-string val))) 611 (cons (convert-to-octets (first val))) 612 (otherwise (babel:string-to-octets (princ-to-string val))))))) 614 (defun write-as-octets (stream val) 616 ((array (unsigned-byte 8) (*)) (write-sequence val stream)) 618 (with-open-file (in val :element-type '(unsigned-byte 8)) 619 (copy-stream in stream))) 621 (write-sequence (convert-to-octets val) stream)) 622 (cons (write-as-octets stream (first val))) 623 (otherwise (fast-write-sequence (convert-to-octets val) stream)))) 625 (defun content-length (val) 627 (pathname (with-open-file (in val) 629 (cons (content-length (first val))) 630 (otherwise (length (convert-to-octets val))))) 632 (defun multipart-content-length (content boundary) 633 (declare (type simple-string boundary)) 634 (let ((boundary-length (length boundary))) 635 (+ (loop for (key . val) in content 639 (length (the simple-string (content-disposition key val))) 640 (let ((content-type (multipart-value-content-type val))) 642 (+ #.(length "Content-Type: ") (length content-type) 2) 648 finally (return total-length)) 649 2 boundary-length 2 2))) 651 (defun write-multipart-content (content boundary stream) 652 (let ((boundary (string-to-octets boundary))) 653 (labels ((boundary-line (&optional endp) 654 (fast-write-sequence (string-to-octets "--") stream) 655 (fast-write-sequence boundary stream) 657 (fast-write-sequence (string-to-octets "--") stream)) 659 (crlf () (fast-write-sequence +crlf+ stream))) 660 (loop for (key . val) in content 662 (fast-write-sequence (string-to-octets (content-disposition key val)) stream) 663 (let ((content-type (multipart-value-content-type val))) 667 (format nil "Content-Type: ~A~C~C" content-type #\Return #\Newline)) 670 (write-as-octets stream val) 673 (boundary-line t))))) 675 (defun decompress-body (content-encoding body) 676 (unless content-encoding 677 (return-from decompress-body body)) 680 ((string= content-encoding "gzip") 682 (chipz:make-decompressing-stream :gzip body) 683 (chipz:decompress nil (chipz:make-dstate :gzip) body))) 684 ((string= content-encoding "deflate") 686 (chipz:make-decompressing-stream :zlib body) 687 (chipz:decompress nil (chipz:make-dstate :zlib) body))) 691 (defvar *use-connection-pool* t) 692 (defvar *max-active-connections* 8 693 "Allowed number of active connections to all hosts. If you change this, 694 then call (make-new-connection-pool).") 696 (defstruct lru-pool-elt 697 (prev nil :type (or null lru-pool-elt)) 698 (next nil :type (or null lru-pool-elt)) 701 (eviction-callback nil :type (or null function))) 703 ;; An LRU-POOL can have multiple entries for the same key 705 (lock #+sb-thread (sb-thread:make-mutex :name "connection pool lock") 707 (hash-table nil :type (or null hash-table)) ;; hash table entries are lists of elements 708 (head nil :type (or null lru-pool-elt)) ;; most recently used is here and it's a doubly-linked-list 709 (tail nil :type (or null lru-pool-elt)) ;; least recently used is here 710 (num-elts 0 :type fixnum) 711 (max-elts 8 :type fixnum)) 713 (defun make-connection-pool (&optional (max-active-connections *max-active-connections*)) 714 (make-lru-pool :hash-table (make-hash-table :test 'equal) :max-elts max-active-connections)) 716 (defvar *connection-pool* nil) 718 (defun make-new-connection-pool (&optional (max-active-connections *max-active-connections*)) 719 (clear-connection-pool) 720 (setf *connection-pool* (make-connection-pool max-active-connections))) 722 (defun get-from-lru-pool (lru-pool key) 723 "Takes an element from the LRU-POOL matching KEY. Must be called with LRU-POOL-LOCK held. 724 The element is removed from the pool." 725 (let* ((hash-table (lru-pool-hash-table lru-pool)) 726 (possible-elts (gethash key (lru-pool-hash-table lru-pool)))) 728 (let ((remaining-elts (cdr possible-elts))) 730 (setf (gethash key hash-table) remaining-elts) 731 (remhash key hash-table))) 732 (let ((elt (car possible-elts))) 733 (let ((prev (lru-pool-elt-prev elt)) 734 (next (lru-pool-elt-next elt))) 736 (setf (lru-pool-elt-next prev) next) 737 (setf (lru-pool-head lru-pool) next)) 739 (setf (lru-pool-elt-prev next) prev) 740 (setf (lru-pool-tail lru-pool) prev))) 741 (decf (lru-pool-num-elts lru-pool)) 742 (lru-pool-elt-elt elt))))) 744 (defun evict-tail (lru-pool) 745 "Removes the least recently used element of the LRU-POOL and returns 746 (values evicted-element eviction-callback t) if there was 747 an element to remove, otherwise nil. Must be called with LRU-POOL-LOCK held. 749 Outside the LRU-POOL-LOCK you must call the returned EVICTION-CALLBACK with the EVICTED-ELEMENT." 750 ;; slightly different from get-from-lru-pool because we want to get rid of the 751 ;; actual oldest element (one could in principle call get-from-lru-pool on 752 ;; (lru-pool-elt-key (lru-pool-tail lru-pool)) if you didn't care 753 (let* ((tail (lru-pool-tail lru-pool))) 755 (let ((prev (lru-pool-elt-prev tail))) 757 (setf (lru-pool-elt-next prev) nil) 758 (setf (lru-pool-head lru-pool) nil)) 759 (setf (lru-pool-tail lru-pool) prev) 760 (let* ((hash-table (lru-pool-hash-table lru-pool)) 761 (key (lru-pool-elt-key tail)) 762 (remaining (cl:delete tail (gethash key hash-table)))) 764 (setf (gethash key hash-table) remaining) 765 (remhash key hash-table)))) 766 (decf (lru-pool-num-elts lru-pool)) 767 (values (lru-pool-elt-elt tail) (lru-pool-elt-eviction-callback tail) t)))) 769 (defun add-to-lru-pool (lru-pool key elt eviction-callback) 770 "Adds ELT to an LRU-POOL with potentially non-unique KEY, potentially evicting another element to 771 make room. EVICTION-CALLBACK will be called with one parameter ELT, when ELT is evicted from the 772 LRU-POOL. ADD-TO-LRU-POOL must be called with LRU-POOL-LOCK held. 774 If an element was evicted to make space, returns (values evicted-elt eviction-callback t) 775 otherwise nil. The EVICTION-CALLBACK should take one parameter, the evicted element." 776 (declare (type lru-pool lru-pool)) 777 (let* ((old-head (lru-pool-head lru-pool)) 778 (lru-pool-elt (make-lru-pool-elt :prev nil :next old-head :elt elt :key key :eviction-callback eviction-callback)) 779 (hash-table (lru-pool-hash-table lru-pool))) 780 (setf (lru-pool-head lru-pool) lru-pool-elt) 781 (push lru-pool-elt (gethash key hash-table)) 783 (setf (lru-pool-elt-prev old-head) lru-pool-elt)) 784 (unless (lru-pool-tail lru-pool) 785 (setf (lru-pool-tail lru-pool) lru-pool-elt)) 786 (when (> (incf (lru-pool-num-elts lru-pool)) (lru-pool-max-elts lru-pool)) 787 (evict-tail lru-pool)))) 789 (defmethod print-object ((obj lru-pool-elt) str) ;; avoid printing loops 790 (print-unreadable-object (obj str :type "LRU-POOL-ELT") 791 (format str "~A NEXT ~A" (lru-pool-elt-key obj) (lru-pool-elt-next obj)))) 793 (defmethod print-object ((obj lru-pool) str) ;; avoid printing loops 794 (print-unreadable-object (obj str :type "LRU-POOL") 796 (loop with lru-pool-elt = (lru-pool-head obj) 798 do (push (list (lru-pool-elt-key lru-pool-elt) (lru-pool-elt-elt lru-pool-elt)) objs) 799 do (setf lru-pool-elt (lru-pool-elt-next lru-pool-elt))) 801 (format str "~A/~A elts~%~{ ~{~A~^: ~}~^~%~}" (lru-pool-num-elts obj) (lru-pool-max-elts obj) objs) 802 (format str "empty"))))) 804 (defmacro with-lock (lock &body body) 805 #+thread-support `(sb-thread:with-mutex (,lock) 807 #-thread-support `(progn ,@body)) 809 (defun push-connection (host-port stream &optional eviction-callback) 810 "Add STREAM back to connection pool with key HOST-PORT. EVICTION-CALLBACK 811 must be a function of a single parameter, and will be called with STREAM 812 if the HOST-PORT/SOCKET pair is evicted from the connection pool." 813 (when *use-connection-pool* 814 (let ((pool *connection-pool*)) 815 (multiple-value-bind (evicted-elt eviction-callback) 816 (with-lock (lru-pool-lock pool) 817 (add-to-lru-pool pool host-port stream eviction-callback)) 818 (and eviction-callback (funcall eviction-callback evicted-elt)) 821 (defun steal-connection (host-port) 822 "Return the STREAM associated with key HOST-PORT" 823 (when *use-connection-pool* 824 (let ((pool *connection-pool*)) 825 (with-lock (lru-pool-lock pool) 826 (get-from-lru-pool pool host-port))))) 828 (defun clear-connection-pool () 829 "Remove all elements from the connection pool, calling their eviction-callbacks." 830 (when *use-connection-pool* 831 (let ((pool *connection-pool*) 832 evicted-element eviction-callback element-was-evicted) 834 (loop for count from 0 835 do (setf (values evicted-element eviction-callback element-was-evicted) 836 (with-lock (lru-pool-lock pool) 838 do (when eviction-callback (funcall eviction-callback evicted-element)) 839 while element-was-evicted))))) 841 (make-new-connection-pool) 844 (with-compilation-unit () 845 (defparameter *ca-bundle* 846 (uiop:native-namestring #P"/etc/ssl/cacert.pem") 847 "The default public root certificates used in requests.") 850 (defun read-until-crlf*2 (stream) 851 (fast-io:with-fast-output (buf) 854 (loop for byte of-type (or (unsigned-byte 8) null) = (read-byte stream nil nil) 856 do (fast-io:fast-write-byte byte buf) 859 until (= byte (char-code #\Return))) 862 (let ((next-byte (read-byte stream nil nil))) 865 (locally (declare (type (unsigned-byte 8) next-byte)) 867 ((= next-byte (char-code #\Newline)) 868 (fast-io:fast-write-byte next-byte buf) 870 ((= next-byte (char-code #\Return)) 871 (fast-io:fast-write-byte next-byte buf) 874 (fast-io:fast-write-byte next-byte buf) 878 (let ((next-byte (read-byte stream nil nil))) 881 (locally (declare (type (unsigned-byte 8) next-byte)) 883 ((= next-byte (char-code #\Return)) 884 (fast-io:fast-write-byte next-byte buf) 887 (fast-io:fast-write-byte next-byte buf) 891 (let ((next-byte (read-byte stream nil nil))) 894 (locally (declare (type (unsigned-byte 8) next-byte)) 896 ((= next-byte (char-code #\Newline)) 897 (fast-io:fast-write-byte next-byte buf)) 898 ((= next-byte (char-code #\Return)) 899 (fast-io:fast-write-byte next-byte buf) 902 (fast-io:fast-write-byte next-byte buf) 908 (make-array 0 :element-type '(unsigned-byte 8))) 910 (defun read-response (stream has-body collect-headers read-body) 911 (let* ((http (make-http-response)) 914 (headers-data (and collect-headers 915 (fast-io:make-output-buffer))) 916 (header-finished-p nil) 919 (transfer-encoding-p) 920 (parser (make-parser http 923 (setq header-finished-p t 924 content-length (gethash "content-length" headers) 925 transfer-encoding-p (gethash "transfer-encoding" headers)) 926 (unless (and has-body 928 transfer-encoding-p)) 931 (lambda (data start end) 933 (fast-io:fast-write-sequence data body-data start end))) 936 (setq finishedp t))))) 937 (let ((buf (read-until-crlf*2 stream))) 938 (declare (type octet-vector buf)) 939 (when collect-headers 940 (fast-io:fast-write-sequence buf headers-data)) 941 (funcall parser buf)) 942 (unless header-finished-p 943 (error "maybe invalid header")) 948 (setq body +empty-body+)) 949 ((and content-length (not transfer-encoding-p)) 950 (let ((buf (make-array (etypecase content-length 951 (integer content-length) 952 (string (parse-integer content-length))) 953 :element-type '(unsigned-byte 8)))) 954 (read-sequence buf stream) 956 ((let ((status (http-status http))) 957 (or (= status 100) ;; Continue 958 (= status 101) ;; Switching Protocols 959 (= status 204) ;; No Content 960 (= status 304))) ;; Not Modified 961 (setq body +empty-body+)) 963 (setq body-data (fast-io:make-output-buffer)) 964 (loop for buf of-type octet-vector = (read-until-crlf*2 stream) 965 do (funcall parser buf) 967 (zerop (length buf))) 969 (setq body (fast-io:finish-output-buffer body-data))))) 973 (fast-io:finish-output-buffer headers-data)) 974 transfer-encoding-p))) 976 (defun print-verbose-data (direction &rest data) 977 (flet ((boundary-line () 978 (let ((char (ecase direction 987 (map nil (lambda (byte) 988 (princ (code-char byte))) 992 (defun convert-body (body content-encoding content-type content-length chunkedp force-binary force-string keep-alive-p on-close) 995 ((and keep-alive-p chunkedp) 996 (setf body (make-keep-alive-stream body :chunked-stream 997 (let ((chunked-stream (chunga:make-chunked-stream body))) 998 (setf (chunga:chunked-stream-input-chunking-p chunked-stream) t) 999 chunked-stream) :on-close-or-eof on-close))) 1000 ((and keep-alive-p content-length) 1001 (setf body (make-keep-alive-stream body :end content-length :on-close-or-eof on-close))) 1003 (let ((chunked-stream (chunga:make-chunked-stream body))) 1004 (setf (chunga:chunked-stream-input-chunking-p chunked-stream) t) 1005 (setf body chunked-stream))))) 1006 (let ((body (decompress-body content-encoding body))) 1009 (decode-body content-type body 1010 :default-charset (if force-string 1011 babel:*default-character-encoding* 1014 (defun content-disposition (key val) 1016 (let* ((filename (file-namestring val)) 1017 (utf8-filename-p (find-if (lambda (char) 1018 (< 127 (char-code char))) 1020 (format nil "Content-Disposition: form-data; name=\"~A\"; ~:[filename=\"~A\"~;filename*=UTF-8''~A~]~C~C" 1024 (obj/uri:parse-uri filename) 1026 #\Return #\Newline)) 1027 (format nil "Content-Disposition: form-data; name=\"~A\"~C~C" 1029 #\Return #\Newline))) 1031 (defun build-cookie-headers (uri cookie-jar) 1032 (with-header-output (buffer) 1033 (let ((cookies (cookie-jar-host-cookies cookie-jar (uri-host uri) (or (uri-path uri) "/") 1034 :securep (string= (uri-scheme uri) "https")))) 1036 (fast-io:fast-write-sequence (string-to-octets "Cookie: ") buffer) 1037 (fast-io:fast-write-sequence 1038 (string-to-octets (write-cookie-header cookies)) 1040 (fast-io:fast-write-sequence +crlf+ buffer))))) 1042 (defun make-connect-stream (uri version stream &optional proxy-auth) 1043 (let ((header (fast-io:with-fast-output (buffer) 1044 (write-connect-header uri version buffer proxy-auth)))) 1045 (write-sequence header stream) 1046 (force-output stream) 1047 (read-until-crlf*2 stream) 1050 (defun make-proxy-authorization (uri) 1051 (let ((proxy-auth (quri:uri-userinfo uri))) 1053 (format nil "Basic ~A" 1054 (cl-base64:string-to-base64-string proxy-auth))))) 1056 (defconstant +socks5-version+ 5) 1057 (defconstant +socks5-reserved+ 0) 1058 (defconstant +socks5-no-auth+ 0) 1059 (defconstant +socks5-connect+ 1) 1060 (defconstant +socks5-domainname+ 3) 1061 (defconstant +socks5-succeeded+ 0) 1062 (defconstant +socks5-ipv4+ 1) 1063 (defconstant +socks5-ipv6+ 4) 1065 (defun ensure-socks5-connected (input output uri http-method) 1066 (labels ((fail (condition &key reason) 1067 (error (make-condition condition 1068 :body nil :status nil :headers nil 1073 (unless (eql n (read-byte input nil 'eof)) 1074 (fail 'socks5-proxy-request-failed :reason reason))) 1077 (when (eq (read-byte input nil 'eof) 'eof) 1078 (fail 'socks5-proxy-request-failed :reason reason))))) 1079 ;; Send Version + Auth Method 1080 ;; Currently, only supports no-auth method. 1081 (write-byte +socks5-version+ output) 1082 (write-byte 1 output) 1083 (write-byte +socks5-no-auth+ output) 1084 (finish-output output) 1086 ;; Receive Auth Method 1087 (exact +socks5-version+ "Unexpected version") 1088 (exact +socks5-no-auth+ "Unsupported auth method") 1090 ;; Send domainname Request 1091 (let* ((host (babel:string-to-octets (uri-host uri))) 1092 (hostlen (length host)) 1093 (port (uri-port uri))) 1094 (unless (<= 1 hostlen 255) 1095 (fail 'socks5-proxy-request-failed :reason "domainname too long")) 1096 (unless (<= 1 port 65535) 1097 (fail 'socks5-proxy-request-failed :reason "Invalid port")) 1098 (write-byte +socks5-version+ output) 1099 (write-byte +socks5-connect+ output) 1100 (write-byte +socks5-reserved+ output) 1101 (write-byte +socks5-domainname+ output) 1102 (write-byte hostlen output) 1103 (write-sequence host output) 1104 (write-byte (ldb (byte 8 8) port) output) 1105 (write-byte (ldb (byte 8 0) port) output) 1106 (finish-output output) 1109 (exact +socks5-version+ "Unexpected version") 1110 (exact +socks5-succeeded+ "Unexpected result code") 1111 (drop 1 "Should be reserved byte") 1112 (let ((atyp (read-byte input nil 'eof))) 1114 ((eql atyp +socks5-ipv4+) 1115 (drop 6 "Should be IPv4 address and port")) 1116 ((eql atyp +socks5-ipv6+) 1117 (drop 18 "Should be IPv6 address and port")) 1118 ((eql atyp +socks5-domainname+) 1119 (let ((n (read-byte input nil 'eof))) 1121 (fail 'socks5-proxy-request-failed :reason "Invalid domainname length")) 1122 (drop n "Should be domainname and port"))) 1124 (fail 'socks5-proxy-request-failed :reason "Unknown address"))))))) 1126 (defun make-ssl-stream (stream ca-path ssl-key-file ssl-cert-file ssl-key-password hostname insecure) 1127 #+(not ssl) (declare (ignore stream ca-path ssl-key-file ssl-cert-file ssl-key-password hostname insecure)) 1128 #+(not ssl) (error "SSL not supported. Remove :dexador-no-ssl from *features* to enable SSL.") 1131 (cl+ssl:ensure-initialized) 1132 (let ((ctx (cl+ssl:make-context :verify-mode 1134 cl+ssl:+ssl-verify-none+ 1135 cl+ssl:+ssl-verify-peer+) 1139 (ca-path (uiop:native-namestring ca-path)) 1140 ((probe-file *ca-bundle*) *ca-bundle*) 1141 ;; In executable environment, perhaps *ca-bundle* doesn't exist. 1143 (ssl-cert-pem-p (and ssl-cert-file 1144 (std/seq:ends-with-subseq ".pem" ssl-cert-file)))) 1145 (cl+ssl:with-global-context (ctx :auto-free-p t) 1146 (when ssl-cert-pem-p 1147 (cl+ssl:use-certificate-chain-file ssl-cert-file)) 1148 (cl+ssl:make-ssl-client-stream stream 1150 :verify (not insecure) 1152 :certificate (and (not ssl-cert-pem-p) 1154 :password ssl-key-password))))) 1156 (defstruct usocket-wrapped-stream 1159 ;; Forward methods the user might want to use on this. 1160 ;; User is not meant to interact with this object except 1161 ;; potentially to close it when they decide they don't 1162 ;; need the :keep-alive connection anymore. 1163 (defmethod close ((u usocket-wrapped-stream) &key abort) 1164 (close (usocket-wrapped-stream-stream u) :abort abort)) 1166 (defmethod open-stream-p ((u usocket-wrapped-stream)) 1167 (open-stream-p (usocket-wrapped-stream-stream u))) 1169 (defun request (uri &rest args 1170 &key (method :get) (version 1.1) 1172 basic-auth bearer-auth 1174 (connect-timeout *default-connect-timeout*) (read-timeout *default-read-timeout*) 1175 (keep-alive t) (use-connection-pool t) 1177 ssl-key-file ssl-cert-file ssl-key-password 1178 stream (verbose *verbose*) 1182 (proxy *default-proxy*) 1186 (proxy-uri (and proxy (quri:uri proxy))) 1187 (original-user-supplied-stream stream) 1188 (user-supplied-stream (if (usocket-wrapped-stream-p stream) (usocket-wrapped-stream-stream stream) stream))) 1189 (declare (ignorable ssl-key-file ssl-cert-file ssl-key-password 1192 (type fixnum max-redirects)) 1193 (with-content-caches 1194 (labels ((make-new-connection (uri) 1196 (let* ((con-uri (uri (or proxy uri))) 1197 (connection (usocket:socket-connect (uri-host con-uri) 1198 (or (uri-port con-uri) 80) 1199 #-(or ecl clasp clisp allegro) :timeout #-(or ecl clasp clisp allegro) connect-timeout 1200 :element-type '(unsigned-byte 8))) 1202 (usocket:socket-stream connection)) 1203 (scheme (uri-scheme uri))) 1204 (declare (type keyword scheme)) 1206 #+lispworks(setf (stream:stream-read-timeout stream) read-timeout) 1207 #-lispworks(setf (usocket:socket-option connection :receive-timeout) read-timeout)) 1208 (when (socks5-proxy-p proxy-uri) 1209 (ensure-socks5-connected stream stream uri method)) 1210 (if (string= (symbol-name scheme) "https") 1211 (make-ssl-stream (if (http-proxy-p proxy-uri) 1212 (make-connect-stream uri version stream (make-proxy-authorization con-uri)) 1213 stream) ca-path ssl-key-file ssl-cert-file ssl-key-password (uri-host uri) insecure) 1216 :report "Retry the same request." 1217 (return-from request 1218 (apply #'request uri :use-connection-pool nil args))) 1220 :report "Retry the same request without checking for SSL certificate validity." 1221 (return-from request 1222 (apply #'request uri :use-connection-pool nil :insecure t args))))) 1225 (let ((scheme (uri-scheme uri))) 1226 (and (stringp scheme) 1227 (or (string= scheme "http") 1228 (string= scheme "https")))))) 1229 (socks5-proxy-p (uri) 1231 (let ((scheme (uri-scheme uri))) 1232 (and (stringp scheme) 1233 (string= scheme "socks5"))))) 1234 (connection-keep-alive-p (connection-header) 1236 (or (and (= (the real version) 1.0) 1237 (equalp connection-header "keep-alive")) 1238 (not (equalp connection-header "close"))))) 1239 (return-stream-to-pool (stream uri) 1240 (push-connection (format nil "~A://~A" 1242 (uri-authority uri)) stream #'close)) 1243 (return-stream-to-pool-or-close (stream connection-header uri) 1244 (if (and (not user-supplied-stream) use-connection-pool (connection-keep-alive-p connection-header)) 1245 (return-stream-to-pool stream uri) 1246 (when (open-stream-p stream) 1248 (finalize-connection (stream connection-header uri) 1249 "If KEEP-ALIVE is in the connection-header and the user is not requesting a stream, 1250 we will push the connection to our connection pool if allowed, otherwise we return 1251 the stream back to the user who must close it." 1254 ((and use-connection-pool (connection-keep-alive-p connection-header) (not user-supplied-stream)) 1255 (return-stream-to-pool stream uri)) 1256 ((not (connection-keep-alive-p connection-header)) 1257 (when (open-stream-p stream) 1258 (close stream))))))) 1259 (let* ((uri (uri uri)) 1260 (proxy (when (http-proxy-p proxy-uri) proxy)) 1261 (content-type (cdr (find :content-type headers :key #'car :test #'string-equal))) 1262 (multipart-p (or (and content-type 1263 (>= (length content-type) 10) 1264 (string= content-type "multipart/" :end1 10)) 1265 (and (not content-type) 1267 (find-if #'pathnamep content :key #'cdr)))) 1268 (form-urlencoded-p (or (string= content-type "application/x-www-form-urlencoded") 1269 (and (not content-type) 1271 (not multipart-p)))) 1272 (boundary (and multipart-p 1273 (make-random-string 12))) 1274 (content (if (and form-urlencoded-p (not (stringp content))) ;; user can provide already encoded content, trust them. 1275 (quri:url-encode-params content) 1277 (stream (or user-supplied-stream 1278 (and use-connection-pool 1279 (steal-connection (format nil "~A://~A" 1281 (uri-authority uri)))))) 1282 (reusing-stream-p (not (null stream))) ;; user provided or from connection-pool 1284 (make-new-connection uri))) 1286 (assoc :content-length headers :test #'string-equal)) 1288 (assoc :transfer-encoding headers :test #'string-equal)) 1289 (chunkedp (or (and transfer-encoding 1290 (equalp (cdr transfer-encoding) "chunked")) 1292 (null (cdr content-length))))) 1294 (fast-io:with-fast-output (buffer) 1295 (write-first-line method uri version buffer))) 1297 (flet ((write-header* (name value) 1298 (let ((header (assoc name headers :test #'string-equal))) 1301 (write-header name (cdr header))) 1302 (write-header name value))) 1304 (with-header-output (buffer) 1305 (write-header* :user-agent #.*default-user-agent*) 1306 (write-header* :host (uri-authority uri)) 1307 (write-header* :accept "*/*") 1310 (= (the real version) 1.0)) 1311 (write-header* :connection "keep-alive")) 1312 ((and (not keep-alive) 1313 (= (the real version) 1.1)) 1314 (write-header* :connection "close"))) 1315 (cond ((and bearer-auth basic-auth) 1316 (error "You should only use one Authorization header.")) 1318 (write-header* :authorization 1319 (format nil "Basic ~A" 1320 (dat/base64::string-to-base64-string 1323 (cdr basic-auth)))))) 1325 (write-header* :authorization 1326 (format nil "Bearer ~A" bearer-auth)))) 1328 (let ((scheme (uri-scheme uri))) 1329 (when (string= scheme "http") 1330 (let* ((uri (uri proxy)) 1331 (proxy-authorization (make-proxy-authorization uri))) 1332 (when proxy-authorization 1333 (write-header* :proxy-authorization proxy-authorization)))))) 1336 (write-header :content-type (format nil "~A; boundary=~A" 1337 (or content-type "multipart/form-data") 1340 (write-header :content-length 1341 (multipart-content-length content boundary)))) 1343 (write-header* :content-type "application/x-www-form-urlencoded") 1345 (write-header* :content-length (length (the string content))))) 1350 (write-header* :content-length 0))) 1352 (write-header* :content-type (or content-type "text/plain")) 1354 (write-header* :content-length (content-length content)))) 1355 ((array (unsigned-byte 8) *) 1356 (write-header* :content-type (or content-type "text/plain")) 1358 (write-header* :content-length (length content)))) 1360 (write-header* :content-type (or content-type (content-type content))) 1362 (write-header :content-length 1363 (or (cdr (assoc :content-length headers :test #'string-equal)) 1364 (content-length content)))))))) 1365 ;; Transfer-Encoding: chunked 1367 (not transfer-encoding)) 1368 (write-header* :transfer-encoding "chunked")) 1371 (loop for (name . value) in headers 1372 unless (member name '(:user-agent :host :accept 1374 :content-type :content-length) :test #'string-equal) 1375 do (write-header name value))))) 1376 (cookie-headers (and cookie-jar 1377 (build-cookie-headers uri cookie-jar)))) 1378 (macrolet ((maybe-try-again-without-reusing-stream (&optional (force nil)) 1379 `(progn ;; retrying by go retry avoids generating the header, parsing, etc. 1380 (when (open-stream-p stream) 1381 (close stream :abort t) 1384 (when ,(or force 'reusing-stream-p) 1385 (setf reusing-stream-p nil 1386 user-supplied-stream nil 1387 stream (make-new-connection uri)) 1389 (try-again-without-reusing-stream () 1390 `(maybe-try-again-without-reusing-stream t)) 1391 (with-retrying (&body body) 1393 (handler-bind (((and error 1394 ;; We should not retry errors received from the server. 1395 ;; Only technical errors such as disconnection or some 1396 ;; problems with the protocol should be retried automatically. 1397 ;; This solves https://github.com/fukamachi/dexador/issues/137 issue. 1398 (not http-request-failed)) 1400 (declare (ignorable e)) 1401 (maybe-try-again-without-reusing-stream)))) 1403 (retry-request () :report "Retry the same request." 1404 (return-from request (apply #'request uri args))) 1405 (ignore-and-continue () :report "Ignore the error and continue.")))) 1409 (unless (open-stream-p stream) 1410 (try-again-without-reusing-stream)) 1413 (write-sequence first-line-data stream) 1414 (write-sequence headers-data stream) 1415 (when cookie-headers 1416 (write-sequence cookie-headers stream)) 1417 (write-sequence +crlf+ stream) 1418 (force-output stream)) 1420 ;; Sending the content 1422 (let ((stream (if chunkedp 1423 (chunga:make-chunked-stream stream) 1426 (setf (chunga:chunked-stream-output-chunking-p stream) t)) 1429 (write-multipart-content content boundary stream) 1430 (write-as-octets stream content)) 1432 (setf (chunga:chunked-stream-output-chunking-p stream) nil)) 1433 (finish-output stream)))) 1436 (multiple-value-bind (http body response-headers-data transfer-encoding-p) 1438 (read-response stream (not (eq method :head)) verbose (not want-stream))) 1439 (let* ((status (http-status http)) 1440 (response-headers (http-headers http)) 1441 (content-length (gethash "content-length" response-headers)) 1442 (content-length (etypecase content-length 1443 (null content-length) 1444 (string (parse-integer content-length)) 1445 (integer content-length)))) 1448 (http-request-failed status 1454 (print-verbose-data :outgoing first-line-data headers-data cookie-headers +crlf+) 1455 (print-verbose-data :incoming response-headers-data)) 1457 (when-let ((set-cookies (append (gethash "set-cookie" response-headers) 1458 (ensure-list (gethash "set-cookie2" response-headers))))) 1459 (net/cookie::merge-cookies cookie-jar 1460 (remove nil (mapcar (lambda (cookie) 1461 (declare (type string cookie)) 1462 (unless (= (length cookie) 0) 1463 (net/cookie:parse-set-cookie-header cookie 1467 (when (and (member status '(301 302 303 307 308) :test #'=) 1468 (gethash "location" response-headers) 1469 (/= max-redirects 0)) 1470 ;; Need to read the response body 1471 (when (and want-stream 1472 (not (eq method :head))) 1474 ((integerp content-length) 1475 (dotimes (i content-length) 1476 (loop until (read-byte body nil nil)))) 1477 (transfer-encoding-p 1478 (read-until-crlf*2 body)))) 1480 (let* ((location-uri (uri (gethash "location" response-headers))) 1481 (same-server-p (or (null (uri-host location-uri)) 1482 (and (string= (uri-scheme location-uri) 1484 (string= (uri-host location-uri) 1486 (eql (uri-port location-uri) 1488 (if (and same-server-p 1489 (or (= status 307) (= status 308) 1490 (member method '(:get :head) :test #'eq))) 1491 (progn ;; redirection to the same host 1492 (setq uri (merge-uris location-uri uri)) 1493 (setq first-line-data 1494 (fast-io:with-fast-output (buffer) 1495 (write-first-line method uri version buffer))) 1497 ;; Rebuild cookie-headers. 1498 (setq cookie-headers (build-cookie-headers uri cookie-jar))) 1499 (decf max-redirects) 1500 (if (equalp (gethash "connection" response-headers) "close") 1501 (try-again-without-reusing-stream) 1503 (setq reusing-stream-p t) 1505 (progn ;; this is a redirection to a different host 1506 (setf location-uri (merge-uris location-uri uri)) 1507 ;; Close connection if it isn't from our connection pool or from the user and we aren't going to 1508 ;; pass it to our new call. 1509 (when (not same-server-p) (return-stream-to-pool-or-close stream (gethash "connection" response-headers) uri)) 1510 (setf (getf args :headers) 1511 (nconc `((:host . ,(uri-host location-uri))) headers)) 1512 (setf (getf args :max-redirects) 1514 ;; Redirect as GET if it's 301, 302, 303 1515 (unless (or (= status 307) (= status 308) 1516 (member method '(:get :head) :test #'eq)) 1517 (setf (getf args :method) :get)) 1518 (return-from request 1519 (apply #'request location-uri (if same-server-p 1521 (progn (remf args :stream) args)))))))) 1523 (let* ((keep-connection-alive (connection-keep-alive-p 1524 (gethash "connection" response-headers))) 1525 (body (convert-body body 1526 (gethash "content-encoding" response-headers) 1527 (gethash "content-type" response-headers) 1532 keep-connection-alive 1533 (if (and use-connection-pool keep-connection-alive (not user-supplied-stream) (streamp body)) 1534 (lambda (underlying-stream abort) 1535 (declare (ignore abort)) 1536 (when (and underlying-stream (open-stream-p underlying-stream)) 1537 ;; read any left overs the user may have not read (in case of errors on user side?) 1538 (loop while (ignore-errors (listen underlying-stream)) ;; ssl streams may close 1539 do (read-byte underlying-stream nil nil)) 1540 (when (open-stream-p underlying-stream) 1541 (push-connection (format nil "~A://~A" 1543 (uri-authority uri)) underlying-stream #'close)))) 1544 #'keep-alive-stream-close-underlying-stream)))) 1545 ;; Raise an error when the HTTP response status code is 4xx or 50x. 1546 (when (<= 400 status) 1548 (http-request-failed status 1550 :headers response-headers 1553 ;; Have to be a little careful with the fifth value stream we return -- 1554 ;; the user may be not aware that keep-alive t without use-connection-pool can leak 1555 ;; sockets, so we wrap the returned last value so when it is garbage 1556 ;; collected it gets closed. If the user is getting a stream back as BODY, 1557 ;; then we instead add a finalizer to that stream to close it when garbage collected 1558 (return-from request 1563 (when (and keep-alive 1564 (not (equalp (gethash "connection" response-headers) "close")) 1565 (or (not use-connection-pool) user-supplied-stream)) 1566 (or (and original-user-supplied-stream ;; user provided a stream 1567 (if (usocket-wrapped-stream-p original-user-supplied-stream) ;; but, it came from us 1568 (eql (usocket-wrapped-stream-stream original-user-supplied-stream) stream) ;; and we used it 1569 (eql original-user-supplied-stream stream)) ;; user provided a bare stream 1570 original-user-supplied-stream) ;; return what the user sent without wrapping it 1571 (if want-stream ;; add a finalizer to the body to close the stream 1573 (trivial-garbage:finalize body (lambda () (close stream))) 1575 (let ((wrapped-stream (make-usocket-wrapped-stream :stream stream))) 1576 (trivial-garbage:finalize wrapped-stream (lambda () (close stream))) 1577 wrapped-stream))))))) 1578 (finalize-connection stream (gethash "connection" response-headers) uri))))))))))) 1581 (defun get (uri &rest args 1582 &key version headers basic-auth bearer-auth cookie-jar keep-alive use-connection-pool 1583 connect-timeout read-timeout max-redirects 1584 force-binary force-string want-stream content 1585 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1586 "Make a GET request to URI and return 1587 (values body-or-stream status response-headers uri &optional opaque-socket-stream) 1589 You may pass a real stream in as STREAM if you want us to communicate with the server via it -- 1590 though if any errors occur, we will open a new connection to the server. If you have a previous 1591 OPAQUE-SOCKET-STREAM you can pass that in as STREAM as well and we will re-use that connection. 1593 OPAQUE-SOCKET-STREAM is not returned if USE-CONNECTION-POOL is T, instead we keep track of it and 1594 re-use it when needed. 1596 If WANT-STREAM is T, then a STREAM is returned as the first value. You may read this as needed to 1597 get the body of the response. If KEEP-ALIVE and USE-CONNECTION-POOL are T, then the stream will be 1598 returned to the connection pool when you have read all the data or closed the stream. If KEEP-ALIVE 1599 is NIL then you are responsible for closing the stream when done. 1601 If KEEP-ALIVE is T and USE-CONNECTION-POOL is NIL, then the fifth value returned is a stream which 1602 you can then pass in again using the STREAM option to re-use the active connection. If you ignore 1603 the stream, it will get closed during garbage collection. 1605 If KEEP-ALIVE is T and USE-CONNECTION-POOL is T, then there is no fifth 1606 value (OPAQUE-SOCKET-STREAM) returned, but the active connection to the host/port may be reused in 1607 subsequent calls. This removes the need for the caller to keep track of the active socket-stream 1608 for subsequent calls. 1610 While CONTENT is allowed in a GET request the results are ill-defined and not advised." 1611 (declare (ignore version headers basic-auth bearer-auth cookie-jar keep-alive use-connection-pool 1612 connect-timeout read-timeout max-redirects force-binary force-string want-stream 1613 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path content)) 1614 (apply #'request uri :method :get args)) 1616 (defun post (uri &rest args 1617 &key version content headers basic-auth bearer-auth cookie-jar keep-alive 1618 use-connection-pool connect-timeout read-timeout 1619 force-binary force-string want-stream 1620 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1621 (declare (ignore version content headers basic-auth bearer-auth cookie-jar keep-alive 1622 use-connection-pool connect-timeout read-timeout force-binary force-string 1623 want-stream ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy 1625 (apply #'request uri :method :post args)) 1627 (defun head (uri &rest args 1628 &key version headers basic-auth bearer-auth cookie-jar connect-timeout read-timeout max-redirects 1629 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1630 (declare (ignore version headers basic-auth bearer-auth cookie-jar connect-timeout read-timeout 1631 max-redirects ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path)) 1632 (apply #'request uri :method :head :use-connection-pool nil args)) 1634 (defun put (uri &rest args 1635 &key version content headers basic-auth bearer-auth cookie-jar keep-alive 1636 use-connection-pool connect-timeout read-timeout 1637 force-binary force-string want-stream 1638 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1639 (declare (ignore version content headers basic-auth bearer-auth cookie-jar keep-alive 1640 use-connection-pool connect-timeout read-timeout force-binary force-string 1641 want-stream ssl-key-file ssl-cert-file ssl-key-password stream verbose 1642 proxy insecure ca-path)) 1643 (apply #'request uri :method :put args)) 1645 (defun patch (uri &rest args 1646 &key version content headers basic-auth bearer-auth cookie-jar keep-alive 1647 use-connection-pool connect-timeout read-timeout 1648 force-binary force-string want-stream 1649 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1650 (declare (ignore version content headers basic-auth bearer-auth cookie-jar keep-alive 1651 use-connection-pool connect-timeout read-timeout force-binary force-string 1652 want-stream ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy 1654 (apply #'request uri :method :patch args)) 1656 (defun delete (uri &rest args 1657 &key version headers basic-auth bearer-auth cookie-jar keep-alive 1658 use-connection-pool connect-timeout read-timeout 1659 force-binary force-string want-stream content 1660 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1661 (declare (ignore version headers basic-auth bearer-auth cookie-jar keep-alive use-connection-pool 1662 connect-timeout read-timeout force-binary force-string want-stream ssl-key-file 1663 ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path content)) 1664 (apply #'request uri :method :delete args)) 1666 (defun fetch (uri destination &rest args 1667 &key (if-exists :error) 1668 version headers basic-auth bearer-auth cookie-jar keep-alive use-connection-pool 1669 connect-timeout read-timeout max-redirects 1670 ssl-key-file ssl-cert-file ssl-key-password stream verbose proxy insecure ca-path) 1671 (declare (ignore version headers basic-auth bearer-auth cookie-jar keep-alive use-connection-pool 1672 connect-timeout read-timeout max-redirects ssl-key-file ssl-cert-file 1673 ssl-key-password stream verbose proxy insecure ca-path)) 1674 (unless (and (eql if-exists nil) 1675 (probe-file destination)) 1676 (with-open-file (out destination 1677 :direction :output :element-type '(unsigned-byte 8) 1678 :if-exists if-exists 1679 :if-does-not-exist :create) 1680 (remf args :if-exists) 1681 (let ((body (apply #'dex:get uri :want-stream t :force-binary t 1683 (alexandria:copy-stream body out) 1684 ;; Nominally the body gets closed, but if keep-alive is nil we need to explicitly do it. 1685 (when (open-stream-p body) 1688 (defun ignore-and-continue (e) 1689 (let ((restart (find-restart 'ignore-and-continue e))) 1691 (invoke-restart restart)))) 1693 (defun retry-request (times &key (interval 3)) 1694 (declare (type (or function integer) interval)) 1697 (let ((restart (find-restart 'retry-request times))) 1699 (invoke-restart restart)))) 1701 (retry-request-ntimes times :interval interval)))) 1703 (defun retry-request-ntimes (n &key (interval 3)) 1704 (declare (type integer n) 1705 (type (or function integer) interval)) 1707 (declare (type integer retries)) 1709 (declare (type condition e)) 1710 (let ((restart (find-restart 'retry-request e))) 1715 (function (funcall interval retries)) 1716 (integer (sleep interval))) 1717 (invoke-restart restart)))))))