/src/core/server.lisp

http://github.com/sykopomp/chillax · Lisp · 166 lines · 128 code · 16 blank · 22 comment · 5 complexity · 52d5921374624869f625385c374cc2c6 MD5 · raw file

  1. (in-package :chillax.core)
  2. ;;;
  3. ;;; Status codes
  4. ;;;
  5. (defparameter +status-codes+
  6. '((200 . :ok)
  7. (201 . :created)
  8. (202 . :accepted)
  9. (304 . :not-modified)
  10. (400 . :bad-request)
  11. (404 . :not-found)
  12. (405 . :resource-not-allowed)
  13. (409 . :conflict)
  14. (412 . :precondition-failed)
  15. (415 . :bad-content-type)
  16. (500 . :internal-server-error))
  17. "A simple alist of keyword names for HTTP status codes, keyed by status code.")
  18. (defparameter +utf-8+ (make-external-format :utf-8 :eol-style :lf))
  19. ;;;
  20. ;;; Conditions
  21. ;;;
  22. (define-condition couchdb-error () ())
  23. (define-condition unexpected-response (couchdb-error)
  24. ((status-code :initarg :status-code :reader error-status-code)
  25. (response :initarg :response :reader error-response))
  26. (:report (lambda (condition stream)
  27. (format stream "Unexpected response with status code: ~A~@
  28. HTTP Response: ~A~@
  29. Please report this to Chillax's maintainer(s)"
  30. (error-status-code condition)
  31. (error-response condition)))))
  32. ;;;
  33. ;;; Server API
  34. ;;;
  35. ;; Server Protocol
  36. (defgeneric server-host (server))
  37. (defgeneric server-port (server))
  38. (defgeneric server-username (server))
  39. (defgeneric server-password (server))
  40. (defgeneric server-secure-p (server))
  41. (defgeneric data->json (server data &key)
  42. (:documentation "Converts DATA to JSON suitable for sending to CouchDB."))
  43. (defgeneric json->data (server json &key)
  44. (:documentation "Converts JSON to the desired data structure."))
  45. (defgeneric make-db-object (server name)
  46. (:documentation
  47. "Creates an object which represents a database connection in SERVER. The object must conform to the
  48. database protocol."))
  49. (defun couch-request (server uri &rest all-keys
  50. &key (content nil contentp) (convert-data-p t)
  51. &allow-other-keys)
  52. "Sends an HTTP request to the CouchDB server represented by SERVER. Most of the keyword arguments
  53. for drakma:http-request are available as kwargs for this message."
  54. (let* ((content (cond ((and contentp convert-data-p)
  55. (data->json server content))
  56. ((and contentp (not convert-data-p))
  57. content)
  58. (t "")))
  59. (content-length (or (getf all-keys :content-length)
  60. (flex:octet-length content :external-format +utf-8+))))
  61. (remf all-keys :content-length)
  62. (remf all-keys :convert-data-p)
  63. (multiple-value-bind (response status-code)
  64. (apply #'http-request (strcat (server-uri server) uri)
  65. :content-type "application/json"
  66. :external-format-out +utf-8+
  67. :external-format-in +utf-8+
  68. :basic-authorization (when (server-username server)
  69. (list (server-username server)
  70. (server-password server)))
  71. :content content
  72. :content-length content-length
  73. all-keys)
  74. (values (json->data server response)
  75. (or (cdr (assoc status-code +status-codes+ :test #'=))
  76. ;; The code should never get here once we know all the
  77. ;; status codes CouchDB might return.
  78. (error "Unknown status code: ~A. HTTP Response: ~A"
  79. status-code response))))))
  80. ;; Server functions
  81. (defun server-uri (server)
  82. "Returns a string representation of the URL SERVER represents."
  83. (format nil "~A://~A:~A/"
  84. (if (server-secure-p server)
  85. "https"
  86. "http")
  87. (server-host server)
  88. (server-port server)))
  89. (defun all-dbs (server)
  90. "Requests a list of all existing databases from SERVER."
  91. (couch-request server "_all_dbs"))
  92. (defun config-info (server)
  93. "Requests the current configuration from SERVER."
  94. (couch-request server "_config"))
  95. (defun replicate (server source target &key create-target-p continuousp
  96. &aux (to-json `("source" ,source "target" ,target)))
  97. "Replicates the database in SOURCE to TARGET. SOURCE and TARGET can both be either database names
  98. in the local server, or full URLs to local or remote databases. If CREATE-TARGET-P is true, the
  99. target database will automatically be created if it does not exist. If CONTINUOUSP is true, CouchDB
  100. will continue propagating any changes in SOURCE to TARGET."
  101. ;; There are some caveats to the keyword arguments -
  102. ;; create-target-p: doesn't actually seem to work at all in CouchDB 0.10
  103. ;; continuousp: The CouchDB documentation warns that this continuous replication
  104. ;; will only last as long as the CouchDB daemon is running. If the
  105. ;; daemon is restarted, replication must be restarted as well.
  106. ;; Note that there are plans to add 'persistent' replication.
  107. (when create-target-p (setf to-json (append `("create_target" "true") to-json)))
  108. (when continuousp (setf to-json (append `("continuous" "true") to-json)))
  109. (couch-request server "_replicate" :method :post :content (format nil "{~{~s:~s~^,~}}" to-json)))
  110. (defun stats (server)
  111. "Requests general statistics from SERVER."
  112. (couch-request server "_stats"))
  113. (defun active-tasks (server)
  114. "Lists all the currently active tasks on SERVER."
  115. (couch-request server "_active_tasks"))
  116. (defun get-uuids (server &key (number 10))
  117. "Returns a list of NUMBER unique IDs requested from SERVER. The UUIDs generated by the server are
  118. reasonably unique, but are not checked against existing UUIDs, so conflicts may still happen."
  119. (couch-request server (format nil "_uuids?count=~A" number)))
  120. ;;;
  121. ;;; Sample protocol implementation
  122. ;;;
  123. (defclass standard-server ()
  124. ((host
  125. :reader server-host
  126. :initarg :host)
  127. (port
  128. :reader server-port
  129. :initarg :port)
  130. (username
  131. :reader server-username
  132. :initarg :username)
  133. (password
  134. :reader server-password
  135. :initarg :password)
  136. (securep
  137. :reader server-secure-p
  138. :initarg :securep))
  139. (:documentation
  140. "Default implementation of the server protocol.")
  141. (:default-initargs
  142. :host "127.0.0.1"
  143. :port 5984
  144. :username nil
  145. :password nil
  146. :securep nil))
  147. (defmethod data->json ((server standard-server) data &key)
  148. data)
  149. (defmethod json->data ((server standard-server) json &key)
  150. json)