Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/server-side-event-stream/graph-store.lisp

KindCoveredAll%
expression0123 0.0
branch06 0.0
Key
Not instrumented
Conditionalized out
Executed
Not executed
 
Both branches taken
One branch taken
Neither branch taken
1
 ;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.implementation; -*-
2
 ;;; (load "patches/20161125-event-stream/server/graph-store.lisp")
3
 
4
 (:documentation "server side event streams"
5
                 "The 'server side event' protocol[1,2,3] descibes a request/response exchange over HTTP
6
  with a variant response content encoding to permit a server to respond to a request with by repeating
7
  the request and emitting the successive response as 'events'.
8
 
9
  This process suffices to source query results as streams.
10
  The implementation specializes graph-store response methods for the sparql and graph store
11
  endpoints based on the text/event-stream response media type.
12
  The methods repeat the respective base operation once per revision.
13
 ---
14
 [1] : http://www.w3.org/TR/eventsource/
15
 [2] : https://html.spec.whatwg.org/multipage/comms.html#server-sent-events
16
 [3] : https://en.wikipedia.org/wiki/Server-sent_events
17
 [4] : https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
18
 ")
19
 
20
 (in-package :spocq.si)
21
 
22
 
23
 (defmethod graph-store-response :head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type MIME:text/event-stream))
24
     (graph-store-head resource request response request-type response-type))
25
 
26
 ;;; needed in order for it to permit the accept specification
27
 (defmethod graph-store-response :encode ((resource t) request response
28
                                          (request-type t) (response-content-type MIME:text/event-stream))
29
   (call-next-method))
30
 
31
 (defmethod graph-store-response :get ((resource |/:account/:repository/sparql|) request response
32
                                       (request-type null) (response-content-type MIME:text/event-stream))
33
     ;; re-iterate the query request across the specified revisions
34
     (let ((query (http:request-query-argument request "query")))
35
       (if (plusp (length query))
36
           (graph-store-stream-query resource query request response request-type response-content-type)
37
           (http:bad-request "The request must include a query."))))
38
 
39
 ;;; (mime:mime-type "text/csv; accept=application/sparql-results")
40
 
41
 (defmethod graph-store-response :post ((resource spocq.si::|/:account/:repository/sparql|) request response
42
                                        (request-type mime:application/sparql) (response-content-type MIME:text/event-stream))
43
     ;; given a body with just the query text accept the entire body.
44
   ;(break "in spocq.si::graph-store-response")
45
     (let ((query (http:request-body request)))
46
       (if (plusp (length query))
47
           (graph-store-stream-query resource query request response request-type response-content-type)
48
           (http:bad-request "The request must include a query."))))
49
 
50
 
51
 (defun graph-store-stream-query (resource query request response request-type response-content-type)
52
   (declare (ignore request-type))
53
   (setf (http:response-header response :Access-Control-Expose-Headers) "*")
54
 
55
   (setf (http:response-header response :Access-Control-Allow-Origin) "http://stage.dydra.com")
56
   (setf (http:response-header response :Access-Control-Allow-Credentials) "true")
57
   (let ((stream-content-type (or (mime:mime-type-parameter response-content-type :accept)
58
                                  mime:application/sparql-results+json))
59
         (revision-ids (spocq.i::repository-revision-ids (resource-repository resource)))
60
         (response-stream (http:response-content-stream response)))  ;; nb. emits the http headers
61
     (unless (typep stream-content-type 'mime:*/*) ()
62
       (http:bad-request "Invalid event stream accept type: ~s" stream-content-type))
63
     (flet ((reiterate-query (resource)
64
              (graph-store-query resource query request response mime:application/sparql-query stream-content-type)))
65
       (with-event-stream (e-stream response-stream)
66
         (let ((output-stream (make-instance 'DE.SETF.HTTP:OUTPUT-STREAM :real-stream e-stream))
67
               (*print-pretty* nil))
68
           (setf (http:response-content-stream response) output-stream)
69
           (loop for spocq.i::*revision-id* in revision-ids
70
             for revision-resource = (make-instance (class-of resource)
71
                                       :account (spocq.i::account-name (resource-account (resource-account-resource resource)))
72
                                       :repository (spocq.i::repository-name (resource-repository resource))
73
                                       :revision-id spocq.i::*revision-id*)
74
             do (progn
75
                  (stream-write-header e-stream "Content-Type" (string (type-of stream-content-type)))
76
                  (stream-write-header e-stream "Revision-ID" spocq.i::*revision-id*)
77
                  (let* ((modification-time (or (dydra:repository-write-date (resource-repository revision-resource)) (get-universal-time)))
78
                         (rfc1123-modification-time (http:encode-rfc1123 modification-time)))
79
                    (stream-write-header e-stream "Memento-Datetime" rfc1123-modification-time))
80
                  (reiterate-query revision-resource)
81
                  (fresh-line e-stream)
82
                  (terpri e-stream)
83
                  (finish-output response-stream))
84
             finally (progn (format response-stream "data: .~%~%"))))))))
85
 
86
 
87
 #+(or)
88
 (progn ;; should not be necessary with precedence reordered to have the content precede the encoding
89
 (defmethod spocq.i::send-response-message ((op (eql  :|response|)) (content spocq.i::query) destination (media-type MIME:APPLICATION/SPARQL-RESULTS+JSON))
90
   (spocq.i::send-response-method op (spocq.i::task-result-generator content) destination media-type))
91
 
92
 (defmethod spocq.i::WRITE-SPARQL-RESULTS+JSON ((content spocq.i::query) stream)
93
   (spocq.i::WRITE-SPARQL-RESULTS+JSON (spocq.i::task-result-generator content) stream))
94
 )
95
 
96
 #+(or)
97
 (let* ((request (allocate-instance (find-class 'HUNCHENTOOT:TBNL-REQUEST)))
98
        (acceptor (allocate-instance (find-class 'hunchentoot:acceptor)))
99
        (HUNCHENTOOT:*ACCEPTOR* acceptor)
100
        (response (make-instance (find-class 'HUNCHENTOOT:TBNL-RESPONSE)
101
                    :content-stream (make-instance 'DE.SETF.HTTP:OUTPUT-STREAM :real-stream *trace-output*)
102
                    :acceptor acceptor
103
                    :request request)))
104
   (setf (slot-value request 'method) :get
105
         (slot-value request 'DE.SETF.HTTP.IMPLEMENTATION::AGENT) (make-instance 'spocq.i::administrator :name "system")
106
         (slot-value request 'HUNCHENTOOT:SERVER-PROTOCOL) "http 1.1"
107
         (slot-value request 'HUNCHENTOOT:get-parameters) ())
108
   (setf (slot-value acceptor 'HUNCHENTOOT::OUTPUT-CHUNKING-P) nil
109
         (slot-value acceptor 'HUNCHENTOOT::PERSISTENT-CONNECTIONS-P) t
110
         (slot-value request 'HUNCHENTOOT:HEADERS-IN) nil
111
         (http:response-media-type response) MIME:text/event-stream)
112
   (graph-store-stream-query (make-instance 'spocq.si::|/:account/:repository/sparql| :identifier <http://localhost/james/test/sparql>
113
                               :account "james" :repository "test")
114
                             "select count(*) where {?s ?p ?o}"
115
                             request 
116
                             response
117
                             nil
118
                             (mime-type "text/event-stream; accept=application/sparql-results+json")))
119
 
120
 ;;; curl -v -k -H "Accept: text/event-stream; accept=APPLICATION/SPARQL-RESULTS+JSON" 'https://stage.dydra.com:81/james/test/sparql?query=select%20count(*)%20where%20%7B%3Fs%20%3Fp%20%3Fo%7D'
121
 
122
 
123
 
124
 
125
 
126
 
127