Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/server-side-event-stream/graph-store.lisp
| Kind | Covered | All | % |
| expression | 0 | 123 | 0.0 |
| branch | 0 | 6 | 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")
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'.
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.
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
20
(in-package :spocq.si)
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))
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))
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."))))
39
;;; (mime:mime-type "text/csv; accept=application/sparql-results")
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."))))
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) "*")
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))
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*)
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)
83
(finish-output response-stream))
84
finally (progn (format response-stream "data: .~%~%"))))))))
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))
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))
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*)
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}"
118
(mime-type "text/event-stream; accept=application/sparql-results+json")))
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'