Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/graph-store.lisp
| Kind | Covered | All | % |
| expression | 1038 | 2645 | 39.2 |
| branch | 81 | 246 | 32.9 |
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.server.implementation; -*-
2
;;; (load #p"patches/graph-store.lisp")
4
(in-package :org.datagraph.spocq.server.implementation)
6
(:documentation "implement the sparql graph store protocol plus extensions, variations, etc.
8
This file implements ths responses to the graph store protocol, some of which are delegated from the sesame
9
protocol operators. The basoc compact graph store patterns provide less encumbered means
10
to address the resource and its content:
12
${STORE_URL}/${STORE_ACCOUNT}/
14
${STORE_REPOSITORY}/service?default : the default graph
15
${STORE_REPOSITORY}/service?graph=${STORE_IGRAPH} : an arbitrary indirect graph
16
/service?graph=urn:dydra:service-description : the repository SPARQL endpoint service description
17
${STORE_REPOSITORY}/${STORE_RGRAPH} : graph relative to the repository base url
18
that is, anything which is not otherwise matched to a static
19
route, wil be treated as a graph store request for that concrete reaph
20
${STORE_REPOSITORY}/sparql : the general sparql path is implemented here as well
21
${STORE_REPOSITORY}/${VIEW} : a view path replaces sparql with the view name
24
;;; (load "de/setf/utility/clos/print-generic-graph.lisp")
25
;;; (dsu:graph-function 'spocq.si::graph-store-response #p"/tmp/graph-store-response.dot")
27
(defmethod initialize-instance ((instance |/:account/:repository/:graph|) &rest initargs &key
28
(request (error "request is required.")))
29
(apply #'call-next-method instance
30
:graph (request-resource-url request)
33
(defparameter *post-new-graph-content-type* 'mime:triples)
34
(defparameter *patch-new-graph-content-type* 'mime:triples)
35
(defparameter *graph-store-response.task-id* nil)
36
;;; (setq *graph-store-response.task-id* (make-v1-uuid-string))
38
;; nb, the <view>.json path is used be the web intreface to update query texts
39
(defun graph-store-file-type-media-type (type)
40
(cond ((equalp type "circos+svg")
41
mime:image/vnd.dydra.sparql-results+circos+svg+xml)
46
((equalp type "dot+svg")
47
mime:image/vnd.graphviz+svg+xml)
48
((or (equalp type "html") (equalp type "htm"))
51
mime:application/vnd.hdt)
52
((equalp type "jsonp")
53
mime:application/javascript)
55
mime:application/sparql-query-algebra)
57
mime:application/VND.DYDRA.SPARQL-QUERY-PLAN)
59
mime:application/VND.DYDRA.SPARQL-QUERY-ALGEBRA)
61
mime:text/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ)
62
((equalp type "rqs+dot+svg")
63
mime:image/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ+SVG+XML)
64
((equalp type "rqs+dot+pdf")
65
mime:application/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ+PDF)
67
mime:application/VND.DYDRA.SPARQL-RESULTS-TRACE)
69
mime:application/VND.DYDRA.SPARQL-RESULTS-TRACE+JSON)
71
mime:text/VND.DYDRA.SPARQL-RESULTS-EXECUTION+GRAPHVIZ)
73
mime:application/SPARQL-RESULTS+JSON)
75
mime:application/SPARQL-RESULTS+XML)
77
mime:application/VND.DYDRA.SPARQL-RESULTS-EXECUTION+JSON)
78
((equalp type "rqx+dot+svg")
79
mime:image/VND.DYDRA.SPARQL-RESULTS-EXECUTION+GRAPHVIZ+SVG+XML)
80
((equalp type "src+svg")
81
mime:image/VND.DYDRA.SPARQL-RESULTS+CIRCOS+SVG+XML)
82
((equalp type "src+png")
83
mime:image/VND.DYDRA.SPARQL-RESULTS+CIRCOS+PNG) ;; no pdf
84
((equalp type "srg+svg")
85
mime:image/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+SVG+XML)
86
((equalp type "srg+pdf")
87
mime:application/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+PDF)
88
((equalp type "srg+png")
89
mime:image/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+PNG)
93
mime:application/trix)
95
mime:text/tab-separated-values)
99
(spocq.i::file-type-media-type type nil))
103
(defmethod http:resource-file-type-media-type ((resource resource))
104
(multiple-value-bind (view resource-type)
105
(http:resource-path-name-and-type resource)
106
(declare (ignore view))
107
(let ((media-type (graph-store-file-type-media-type resource-type)))
108
(dydra:log-info "resource media type derived: ~a: ~a: ~a" resource resource-type media-type)
111
(defmethod http:resource-file-type-media-type ((resource |/:account/:repository/service|))
112
(or (call-next-method)
117
(defparameter *ineffective-media-types* (list mime:text/plain mime:text/html mime:*/*)
118
"Enumerates the mime types which originate in browser requests over links.
119
These require to examing other request attributes - url arguments or resource type,
120
in order to determine an effective media type.")
122
(defgeneric graph-store-effective-accept-media-type (resource-type url-content-type accept-content-type)
124
"Iff a specific content type is present from an accept header, then that applies.
125
If, instead, text/plain, test/html or nothing is present, then fall back on an 'accept' parameter in the url.
126
If none is there, than derive a type from the resource filename ending.
127
If none is known for that, return the original given type")
129
(:method :before (resource request media-type)
132
(describe media-type)
133
(print (list :accept (http:request-accept-header request)
134
:url (http:request-uri request))))
135
(:argument-precedence-order accept-content-type resource-type url-content-type)
136
(:method ((resource t) (request t) (accept-content-type null))
137
(compute-graph-store-effective-accept-media-type resource request))
138
(:method ((resource t) (request t) (accept-content-type string))
139
(graph-store-effective-accept-media-type resource request (mime:mime-type accept-content-type)))
140
(:method ((resource t) (request t) (accept-content-type mime:mime-type))
141
(if (find accept-content-type *ineffective-media-types*)
142
(or (compute-graph-store-effective-accept-media-type resource request)
144
accept-content-type)))
146
(defgeneric compute-graph-store-effective-accept-media-type (resource request)
147
(:documentation "For a request which lacks an effective response content type, combine information
148
from the request url to compute one.")
149
(:method ((resource t) (request http:request))
150
(compute-graph-store-effective-accept-media-type resource (http:request-query-argument request "accept")))
151
(:method ((resource t) (url-accept string))
152
(if (plusp (length url-accept))
153
(mime:mime-type url-accept)
154
(compute-graph-store-effective-accept-media-type resource nil)))
155
(:method ((resource http:resource) (url-accept null))
156
(multiple-value-bind (view resource-type)
157
(http:resource-path-name-and-type resource)
158
(declare (ignore view))
159
(compute-graph-store-effective-accept-media-type resource-type url-accept)))
160
(:method ((resource-type string) (url-accept null))
161
(graph-store-file-type-media-type resource-type))
162
(:method ((resource-type null) (url-accept null))
165
(defgeneric compute-query-response-type (text media-type)
166
(:method ((query null) (response-type t))
168
(:method ((text string) (response-type mime:*/xml))
169
(cond ((or (search "construct" text :test #'char-equal)
170
(search "describe" text :test #'char-equal))
171
mime:application/rdf+xml)
173
mime:application/sparql-results+xml)))
174
(:method ((text string) (response-type mime:text/x-graphviz))
175
(cond ((or (search "construct" text :test #'char-equal)
176
(search "describe" text :test #'char-equal))
177
mime:text/vnd.dydra.sparql-results+graphviz)
179
mime:text/vnd.dydra.sparql-results+graphviz)))
180
(:method ((text string) (response-type mime:image/vnd.graphviz+svg+xml))
181
(cond ((or (search "construct" text :test #'char-equal)
182
(search "describe" text :test #'char-equal))
183
mime:image/vnd.dydra.sparql-results+graphviz+svg+xml)
185
mime:image/vnd.dydra.sparql-results+graphviz+svg+xml)))
186
(:method ((text string) (response-type t))
187
(cond ((or (search "construct" text :test #'char-equal)
188
(search "describe" text :test #'char-equal))
189
mime:application/n-triples)
191
mime:application/sparql-results+json))))
193
(defun graph-store-request-query-expression (resource request)
194
(graph-store-request-get-query-expression resource request))
196
(defparameter *all-query-text* "select * where { {?s ?p ?o} union { graph ?g {?s ?p ?o} } }")
198
(defgeneric graph-store-request-get-query-expression (resource request)
199
(:documentation "Take the query either directly from the 'query' form argument or
200
indirectly, from the view named by the 'view' for argument.")
201
(:method ((resource repository-resource) (request http:request))
202
(flet ((get-authorized-view (view-name)
203
(when (plusp (length view-name))
204
(let ((view (spocq.i::make-view :repository (resource-repository resource) :name view-name)))
205
(cond ((spocq.i::read-view-definition view)
206
(if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
207
(spocq.i::view-query view)
208
(http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
209
((equalp (spocq.i::view-name view) "all")
210
*all-query-text*))))))
211
(let* ((path (http:resource-path resource))
212
(split-path (split-string path "/"))
213
(leaf (third split-path)))
215
;; only length three with sparql, service, or a view
216
(cond ((member leaf '("service" "ldp" "ldf") :test 'equalp)
217
(values nil nil leaf))
218
((equalp leaf "sparql")
219
(values (case (http:request-method request)
220
(:get (or (http:request-query-argument request "query")
221
(http:request-query-argument request "update")
222
(get-authorized-view (http:request-query-argument request "view"))))
223
(:post (or (http:request-post-argument request "query")
224
(http:request-post-argument request "update")
225
(get-authorized-view (http:request-post-argument request "view")))))
229
(multiple-value-bind (view-name resource-type)
230
(http:resource-path-name-and-type path)
231
(let ((view-text (or (get-authorized-view view-name)
232
(case (http:request-method request)
233
(:get (or (http:request-query-argument request "query")
234
(http:request-query-argument request "update")
235
(get-authorized-view (http:request-query-argument request "view"))))
236
(:post (or (http:request-post-argument request "query")
237
(http:request-post-argument request "update")
238
(get-authorized-view (http:request-post-argument request "view"))))))))
247
(defparameter *interactive-media-type* (mime:mime-type "text/plain;encoding=utf-8"))
249
(defgeneric graph-store-effective-response-media-type (interactive-request-p media-type &optional replacement)
250
(:documentation "Return the media type to supply for a response given the accepted type.
251
This maps types such as the various algebra representations which specialize the standard values which can
252
be displayed to the respective concrete type.
253
For other types, if a default replacement is provided, that is used and if not the provided type.
254
Take also into account interactive browser request, for which sone form of zext/plain is necessary
255
if the content is to be displayed inline.")
256
(:method ((request http:request) media-type &optional (replacement media-type))
257
(graph-store-effective-response-media-type (http:request-is-interactive request) media-type replacement))
258
(:method ((user-agent string) media-type &optional (replacement media-type))
259
(graph-store-effective-response-media-type (http:request-is-interactive user-agent) replacement))
260
(:method ((interactive-p t) (type MIME:TEXT/VND.GRAPHVIZ) &optional default) (declare (ignore default)) MIME:TEXT/VND.GRAPHVIZ)
261
(:method ((interactive-p t) (type mime:application/pdf) &optional default) (declare (ignore default)) mime:application/pdf)
262
(:method ((interactive-p t) (type mime:image/png) &optional default) (declare (ignore default)) mime:image/png)
263
(:method ((interactive-p t) (type mime:image/jpeg) &optional default) (declare (ignore default)) mime:image/jpeg)
264
(:method ((interactive-p t) (type mime:image/svg+xml) &optional default) (declare (ignore default)) mime:image/svg+xml)
265
(:method ((interactive-p (eql nil)) (type mime:application/sql) &optional default) (declare (ignore default)) mime:application/sql)
266
(:method ((interactive-p (eql t)) (type mime:application/sql) &optional default) (declare (ignore default)) *interactive-media-type*)
267
(:method ((interactive-p (eql nil)) (type mime:application/sparql-query) &optional default) (declare (ignore default)) mime:application/sparql-query)
268
(:method ((interactive-p (eql t)) (type mime:application/sparql-query) &optional default) (declare (ignore default)) *interactive-media-type*)
269
(:method ((interactive-p t) (type t) &optional (replacement type))
270
(or replacement type)))
273
(defparameter *graph-store-response.print-methods* nil)
275
#+(or) ;; patched into agent as the sparql results version was reading the content twice
276
(defmethod spocq.si::graph-store-response :post ((resource repository-resource) request response (request-type t) (response-type t))
277
;; given a body with just the query text accept the entire body.
278
;(break "in spocq.si::graph-store-response")
279
(let ((query (http:request-body request)))
280
(if (plusp (length query))
281
(spocq.si::graph-store-query resource query request response request-type (http:response-media-type response))
282
(http:bad-request "No query supplied."))))
284
;;; note the :around methods, below.
286
(http:def-resource-function graph-store-response (resource request response)
288
(:auth http:authenticate-request-password)
289
(:auth http:authenticate-request-token)
290
(:auth http:authenticate-request-session)
291
(:auth http:authenticate-request-location)
292
;; anonymous it always last resort
294
(:auth http:authorize-request)
296
;; queries may appear as get or post requests with the appropriate combination of content and accept type
297
;; in any case, the query result must be encoded as per accept type
298
(:encode :default mime:application/sparql-results+json)
299
;(:encode mime:application/sparql-results+xml)
300
(:encode mime:sparql-results)
301
(:encode mime:text/csv)
302
(:encode mime:text/html)
303
(:encode mime:text/tab-separated-values)
305
;; graph content requests as get and rdf document type. it must appear as a get request only, without
306
;; content and an appropriate accept type ;; in this case, the query result is streamed from the
308
(:encode mime:text/turtle)
309
(:encode mime:application/json)
310
(:encode mime:application/javascript) ; support jsonp
311
(:encode mime:application/ld+json)
312
(:encode mime:application/link-format)
313
(:encode mime:application/n-quads)
314
(:encode mime:application/n-triples)
315
(:encode mime:application/rdf+json)
316
(:encode mime:application/rdf+xml)
317
(:encode mime:application/trix)
318
(:encode mime:application/sparql-query-algebra)
319
(:encode mime:application/sql)
320
(:encode mime::sparql-query)
321
(:encode mime::sparql-results)
322
(:encode mime:text/x-graphviz)
323
(:encode mime:text/vnd.graphviz)
324
(:encode mime:circos)
325
(:encode (resource request response content-type (response-type mime:application/sparql-query+olog+svg+xml))
326
(multiple-value-bind (query resource-type service view-name)
327
(graph-store-request-query-expression resource request)
328
(declare (ignore resource-type service view-name))
330
(setf (http:response-content-type-header response) mime:image/svg+xml)
331
(spocq.i::send-response-message :olog query (http:response-content-stream response) response-type))
335
(:decode mime:application/graphql)
336
(:decode mime:application/sparql-query)
337
(:decode mime:application/sparql-update)
338
(:decode mime:application/json)
339
(:decode mime:application/x-www-form-urlencoded)
341
;; permit head requests for the standard response media types
342
;; graph store and views
343
(:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:rdf))
344
(graph-store-head resource request response request-type response-type))
345
(:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:sparql-results))
346
(graph-store-head resource request response request-type response-type))
347
(:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:text/csv))
348
(graph-store-head resource request response request-type response-type))
349
(:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:text/tab-separated-values))
350
(graph-store-head resource request response request-type response-type))
352
(:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:rdf))
353
(graph-store-head resource request response request-type response-type))
354
(:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:sparql-results))
355
(graph-store-head resource request response request-type response-type))
356
(:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:text/csv))
357
(graph-store-head resource request response request-type response-type))
358
(:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:text/tab-separated-values))
359
(graph-store-head resource request response request-type response-type))
361
(:options ((resource graph-store-service-resource) request response (request-type t) (response-type t))
362
(graph-store-options resource request response request-type response-type))
363
(:options ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type t))
364
(graph-store-options resource request response request-type response-type))
366
(:around ((resource repository-resource) (request t) (response t) (request-type t) (response-type t))
367
(let* ((repository (resource-repository resource))
368
(repository-id (dydra:repository-id repository)))
369
(if (find repository-id dydra:*disabled-repositories* :test #'string-equal)
370
(http:bad-request "The repository has been disabled: ~s." repository-id)
371
(call-next-method))))
373
(:around ((resource t) (request t) (response t) (request-type t) (response-type t))
374
"- Assert default headers as well as transport security constraint
375
- If a content type is specified for introspection, reflect the implementation
376
- Otherwise, if a request history directory is configured, then arrange to save the request and the response.
377
To this point, the headers have been read, but any request content is still in the stream.
378
- transcribe the request headers
379
- splice a copying stream onto the input stream to save the request content.
380
- splice a copying stream onto the response stream to save the response content and headers."
381
(trace-graph-store-response resource request response request-type response-type)
382
(setf (http:response-header response :Access-Control-Expose-Headers) "*")
383
;; this is absurd, but broswers insist on pre-flight requests
384
;; limit it to OPTIONS?
385
(setf (http:response-header response :Access-Control-Allow-Headers)
386
"Accept, Accept-Asynchronous, Accept-Datetime, Asynchronous-Content-Type, Asynchronous-Location, Asynchronous-Method, Authorization, Content-Disposition, Content-Encoding, Content-Type, Etag, Graph, Introspection-Content-Type, Link, Location, Revision, X-Requested-With")
387
;; "*" is not sufficient: authorization cannot be wildcarded and if autorization is present "*" is taken literally
388
;; see https://fetch.spec.whatwg.org/#http-new-header-syntax
389
;; (setf (http:response-header response :Access-Control-Allow-Headers) "*")
390
(cond ((http:request-authentication request)
391
(setf (http:response-header response :Access-Control-Allow-Origin) (or (http:request-origin request) (dydra:host-name))
392
(http:response-header response :Access-Control-Allow-Methods) "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"))
393
((setf (http:response-header response :Access-Control-Allow-Origin) "*"
394
(http:response-header response :Access-Control-Allow-Methods) (or (http:request-header request :Access-Control-Request-Methods) "*"))))
395
(setf (http:response-header response :Access-Control-Allow-Credentials) "true")
396
(setf (http:response-header response :Access-Control-Max-Age) "86400")
397
(setf (http:response-header response "Client-Request-ID") (request-client-request-id request))
398
(case *transport-security-mode*
399
(:strict ;; the default is to require https
400
(setf (http:response-header response :Strict-Transport-Security) "max-age=31536000"))
402
(let ((introspection-content-type (http:request-header request "Introspection-Content-Type")))
403
(cond (introspection-content-type
404
(setf introspection-content-type
405
(or (mime:mime-type introspection-content-type)
406
(http:bad-request "graph-store-request: invalid introspection media type: ~s" introspection-content-type)))
407
(let* ((methods (compute-applicable-methods #'graph-store-response (list resource request response request-type response-type)))
408
(field (compute-method-solution-field methods)))
409
(setf (http:response-header response :Request-ID) (dydra:make-task-id))
410
(dydra:send-response-message :algebra field (http:response-content-stream response) introspection-content-type)
413
(let ((request-history-directory (request-history-directory))
414
(response-history-directory (response-history-directory)))
415
(labels ((copy-file (from to)
416
(with-open-file (to-stream to :direction :output :if-exists :supersede :if-does-not-exist :create :element-type '(unsigned-byte 8))
417
(with-open-file (from-stream from :direction :input :element-type '(unsigned-byte 8))
418
(http:copy-stream from-stream to-stream))))
419
(respond-with-request-transcript ()
420
(let* ((tmp-request-pathname (tmp-import-pathname "system" "history"))
421
(request-content-stream (http:request-content-stream request)))
422
(multiple-value-prog1 (with-open-file (request-transcript-stream tmp-request-pathname
425
:if-does-not-exist :create
426
:element-type :default)
427
(http:write-request-header request request-transcript-stream)
428
;;(format request-transcript-stream "~C~C" #\Return #\Linefeed)
429
(setf (http:request-content-stream request)
430
(make-echo-stream request-content-stream request-transcript-stream))
431
(cond (response-history-directory
432
(respond-with-response-transcript))
435
(let* ((task-id (or (http:response-header response :Request-ID)
436
*graph-store-response.task-id*)))
438
(let* ((prefix (subseq task-id 0 2))
439
(request-pathname (merge-pathnames (make-pathname :directory `(:relative ,prefix) :name task-id)
440
request-history-directory)))
441
(ensure-directories-exist request-pathname)
442
;; can be a different volume
443
(copy-file tmp-request-pathname request-pathname)
444
(delete-file tmp-request-pathname)
447
(delete-file tmp-request-pathname))))
448
(setf (http:request-content-stream request) request-content-stream))))
449
(respond-with-response-transcript ()
450
(let* ((tmp-response-pathname (tmp-import-pathname "system" "history"))
451
(response-content-stream (http.i::get-response-content-stream response)))
452
(multiple-value-prog1 (with-open-file (response-transcript-stream tmp-response-pathname
455
:if-does-not-exist :create
456
:element-type :default)
457
(setf (http:response-content-stream response)
458
(make-broadcast-stream response-content-stream response-transcript-stream))
460
(let* ((task-id (or (http:response-header response :Request-ID)
461
*graph-store-response.task-id*)))
462
;; only save for transcribed tasks
464
(let* ((prefix (subseq task-id 0 2))
465
(response-pathname (merge-pathnames (make-pathname :directory `(:relative ,prefix) :name task-id)
466
response-history-directory)))
467
(ensure-directories-exist response-pathname)
468
;; can be a different volume
469
(copy-file tmp-response-pathname response-pathname)
470
(delete-file tmp-response-pathname)
473
(delete-file tmp-response-pathname))))
474
(setf (http:response-content-stream response) response-content-stream))))
477
(cond (request-history-directory
478
(respond-with-request-transcript))
479
(response-history-directory
480
(respond-with-response-transcript))
484
(call-next-method)))))
487
;;; (#P"/srv/dydra/runtime/imports/import-system-history-7651FAB6-67E0-11EB-82B7-25371DD4D4B6" #P"/srv/dydra/history/responses/76/7651FAB4-67E0-11EB-82B7-25371DD4D4B6")
489
(:get ((resource |/:account/:repository/git/:git-path*|) request response (request-type null) (response-type t))
490
(graph-store-get-git resource request response response-type))
492
;; for sparql: ask and select
493
;; (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/sparql-results))
494
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type t))
495
;; absent a request type, extract the query text from an url query field
496
;; iff a query/view argument is present, then it is a query. otherwise signal a bad request
497
(let ((query (graph-store-request-query-expression resource request)))
498
(repository-graph-store-sparql (resource-repository resource) resource query
499
request response mime:application/sparql-query (http:response-media-type response))))
501
;; sparql with an rdf type requires special handling.
502
;; it can be a construct or describe query or it may be for the service description -- iff without a query
503
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:rdf))
504
;; iff a query argument is present, then it is a query. otherwise signal a bad request
505
(let ((query (graph-store-request-query-expression resource request)))
506
(repository-graph-store-sparql (resource-repository resource) resource query
507
request response mime:application/sparql-query (http:response-media-type response))
509
(cond ((plusp (length query))
510
(graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response)))
512
;; ensure minimal state
513
(setf (http:response-header response :Request-ID) (dydra:make-task-id))
514
;; send the service description, not (http:bad-request "The request must include a query.")
515
(spocq.i::make-list-solution-field
516
:solutions (spocq.i::service-description-solutions (spocq.i::repository-service-description (resource-repository resource)))
517
:dimensions spocq.i::*construct-dimensions*)))))
519
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime::query))
520
;; absent a request type, extract the query text from an url query field and transcode iff the encoding is compatible
521
(let ((query (graph-store-request-query-expression resource request)))
522
(if (plusp (length query))
523
(if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
524
;;(compute-get-query-algebra-expression resource query request response mime:application/sparql-query (http:response-media-type response))
525
(graph-store-query resource query request response mime:application/sparql-query response-type)
526
(http:not-acceptable "Media type (GET ~s) not supported for SPARQL.query." (type-of response-type)))
527
(http:bad-request "The request must include a query."))))
529
(:get ((resource |/:account/:repository/:view.:type|) request response (request-type t) (response-type t))
530
"Either execute a view.
531
Execute a query as if decoded from content.
532
The response media type should have been derived from the resource file type if one was present.
533
Respond with a Content-Profile which elides any file type.
534
(see https://www.w3.org/TR/dx-prof-conneg/)
535
Its value involves the site name rather than the host name, as that is the abstract profile designator.
536
The Accept-Profile header cannot be supported as that url yields the repository page and that negotiation would involve rails.
537
When it is supported, the profile should permit names and urls.
538
In the latter case, the abstract profiles will have to be mapped to view names"
539
(multiple-value-bind (query resource-type service view-name)
540
(graph-store-request-query-expression resource request)
541
(declare (ignore resource-type service))
543
(let* ((url (request-resource-url request)))
544
(multiple-value-bind (account repository view type)
545
(spocq.i::parse-view-identifier url)
546
(declare (ignore account repository view))
547
(when type (setf url (subseq url 0 (- (length url) (1+ (length type)))))))
548
(setf (http:response-header response :Content-Profile) url))
549
(graph-store-query resource query request response mime:application/sparql-query response-type
550
:view-name view-name))
554
;; need to add get/post requests for ld+json to extract the context from the request haeader
555
;; and provide the headers for the response
557
(:get ((resource |/:account/:repository/service|) request response (request-type null) (response-type mime:rdf))
558
(graph-store-get-graph resource request response request-type response-type
559
:context (resource-graph resource)))
560
(:get ((resource |/:account/:repository/service|) request response (request-type null) (response-type mime:graphviz))
561
(graph-store-get-graph resource request response request-type response-type
562
:context (resource-graph resource)))
564
(:get ((resource |/:account/:repository/service|) request response (request-type t) (response-type mime:rdf))
565
"Respond with graph content. Allow an optional context"
566
(let ((context (resource-graph resource)))
567
(graph-store-get-graph resource request response request-type response-type
570
(:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type mime:sparql-results))
571
"Either execute a view or respond with graph or ldp content, depending on whether the view is found.
572
locate the named view, or use a default one if not found.
573
Execute a query as if decoded.
574
Determine the response media type from the resource file type.
575
Include a content profile header for a view"
576
(multiple-value-bind (query resource-type service view-name)
577
(graph-store-request-query-expression resource request)
578
(declare (ignore resource-type service))
580
(cond ((compute-applicable-methods #'graph-store-query (list resource query request response
581
mime:application/sparql-query
584
(let ((url (request-resource-url request)))
585
(setf (http:response-header response :Content-Profile) url))
586
(graph-store-query resource query request response mime:application/sparql-query response-type
587
:view-name view-name))
589
(http:not-acceptable "Media type (GET ~s) not supported for SPARQL." (type-of response-type)))))
591
;; for sparql results, if the view is not present -> not-found
594
(:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type t))
595
"Either execute a view or respond with graph or ldp content, depending on whether the view is found.
596
locate the named view, or use a default one if not found.
597
Execute a query as if decoded.
598
determine the response media type from the resource file type"
599
(multiple-value-bind (query resource-type service view-name)
600
(graph-store-request-query-expression resource request)
602
(if (compute-applicable-methods #'graph-store-query (list resource query request response
603
mime:application/sparql-query
606
; (graph-store-query resource query request response mime:application/sparql-query resource-media-type)
607
(graph-store-query resource query request response mime:application/sparql-query response-type
608
:view-name view-name)
609
(http:not-acceptable "Media type (GET ~s) not supported for SPARQL." (type-of response-type))))
610
((equalp service "service")
611
(if (compute-applicable-methods #'graph-store-get-graph (list resource request response request-type response-type))
612
(let* ((path (http:resource-path resource))
613
(context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name)
615
(subseq path 0 (- (length path) (1+ (length resource-type))))
617
(graph-store-get-graph resource request response request-type response-type
619
(http:not-acceptable "Media type (GET ~s) not supported for SERVICE." (type-of response-type))))
620
((equalp service "ldp")
621
(cond ((compute-applicable-methods #'linked-data-platform-response (list :get resource request response request-type response-type nil))
622
;; respond to a ldp request w/o content
623
(indirect-linked-data-platform-response :get resource request response request-type response-type nil)
625
(http:not-acceptable "Media type (GET ~s) not supported for LDP." (type-of response-type))))
627
;; fall back on a gsp direct graph retrieval
628
;; if the graph is not present (eg, because a view was renamed) this will response not-found or no-content
629
;; depending on *graph-store-get-is-silent*
630
(graph-store-get-graph resource request response request-type response-type
631
:context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) (http:resource-path resource))))))))
633
(:patch ((resource |/:account/:repository/service|) request response (request-type mime:rdf) response-type)
634
"Given an RDF media type, accept the temporary pathname and the effective
635
content type as normalized from the decoding method and use the store's
636
import path to apply it as a patch.
637
This has the effect to replace the content of any graphs which are present
638
in the request content.
640
(cf. https://tools.ietf.org/html/rfc5789)
642
nb. no support in this combination for direct or indirect contexts"
643
(let* ((location (http:request-header request "Location")))
644
(log-graph-store-service-response resource request location)
645
;; given a location, take the content from there.
646
;; otherwise decode it from the request stream.
647
(labels ((patch-content (pathname effective-request-type)
648
(unwind-protect (graph-store-patch-content resource request response pathname effective-request-type)
649
(conditional-delete-file pathname)))
650
(process-location (stream)
651
(setf (http:request-header request :location) nil)
652
(let ((request-content-stream (http:request-content-stream request)))
653
(unwind-protect (progn (setf (http:request-content-stream request) stream)
654
(multiple-value-call #'patch-content (call-next-method)))
655
(setf (http:request-content-stream request) request-content-stream)))))
656
(cond ((is-http-url-namestring location)
657
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
658
((is-file-url-namestring location)
659
(call-with-import-file-stream #'process-location (construct-iri location)))
661
(multiple-value-call #'patch-content (call-next-method)))))))
663
(:patch ((resource |/:account/:repository/service|) request response (request-type mime:application/sparql) (response-type mime:sparql-results))
664
;; given a body with just the query text accept the entire body.
665
(let ((query (http:request-body request)))
666
(if (plusp (length query))
667
(graph-store-query resource query request response request-type response-type)
668
(http:bad-request "No query supplied."))))
670
(:patch ((resource |/:account/:repository/service|) request response (request-type mime:multipart/*) response-type)
672
(let* ((location (http:request-header request "Location")))
673
(log-graph-store-service-response resource request location)
674
;; given a location, take the content from there: need to leave it verbatim
675
(labels ((patch-content (pathname effective-request-type)
676
(unwind-protect (graph-store-patch-multipart-content resource request response pathname effective-request-type)
677
(conditional-delete-file pathname)))
678
(process-location (stream)
679
(setf (http:request-header request :location) nil)
680
(let ((request-content-stream (http:request-content-stream request)))
681
(unwind-protect (progn (setf (http:request-content-stream request) stream)
682
(multiple-value-call #'patch-content (call-next-method)))
683
(setf (http:request-content-stream request) request-content-stream)))))
684
(cond ((is-http-url-namestring location)
685
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
686
((is-file-url-namestring location)
687
(call-with-import-file-stream #'process-location (construct-iri location)))
689
(multiple-value-call #'patch-content (call-next-method)))))))
692
;;; do _not_ specialize the request type - allow the decode methods to handle the combinations
693
;;; otherwise the combination with text/plain methods are not correct
694
;;; two headers control the processing
695
;;; - Location : determines whether the content is in-line or retrieved
696
;;; - Accept-Asynchronous controls whether processing is immediate or asnychronous
697
(:post ((resource |/:account/:repository/service|) request response (request-type t) (response-type t))
698
(let* ((location (http:request-header request "Location")))
699
(log-graph-store-service-response resource request location)
700
;; introduce a default base uri for triple content
701
(labels ((post-content (pathname effective-request-type)
702
(unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
703
(conditional-delete-file pathname)))
704
(process-location (stream)
705
(setf (http:request-header request :location) nil)
706
(let ((request-content-stream (http:request-content-stream request)))
707
(unwind-protect (progn (setf (http:request-content-stream request) stream)
708
(multiple-value-call #'post-content (call-next-method)))
709
(setf (http:request-content-stream request) request-content-stream)))))
710
(cond ((is-http-url-namestring location)
711
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
712
((is-file-url-namestring location)
713
(call-with-import-file-stream #'process-location (construct-iri location)))
715
(multiple-value-call #'post-content (call-next-method)))))))
717
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:sparql-results))
718
;; given a body with just the query text accept the entire body.
719
(let ((query (http:request-body request)))
720
(if (plusp (length query))
721
(graph-store-query resource query request response request-type response-type)
722
(http:bad-request "No query supplied."))))
724
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:application/json))
725
;; given a body with just the query text accept the entire body. respond with an ideosyncratic encoding
726
(let ((query (http:request-body request)))
727
(if (plusp (length query))
728
(graph-store-query resource query request response request-type response-type)
729
(http:bad-request "No query supplied."))))
731
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:rdf))
732
;; given a body with just the query text accept the entire body.
733
(let ((query (http:request-body request)))
734
(if (plusp (length query))
735
(graph-store-query resource query request response request-type response-type)
736
(http:bad-request "No query supplied."))))
738
;; for ask and select
739
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:sparql-results))
740
;; given a body with just the query text accept the entire body.
741
(let ((query (http:request-body request)))
742
(if (plusp (length query))
743
(graph-store-query resource query request response request-type response-type)
744
(http:bad-request "No query supplied."))))
746
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:application/json))
747
;; given a body with just the query text accept the entire body. respond with an ideosyncratic encoding
748
(let ((query (http:request-body request)))
749
(if (plusp (length query))
750
(graph-store-query resource query request response request-type response-type)
751
(http:bad-request "No query supplied."))))
753
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:sparql-results))
754
;; given a form body, decode it and extract the query
755
(let ((query (graph-store-request-query-expression resource request)))
756
(if (plusp (length query))
757
(graph-store-query resource query request response mime:application/sparql-query response-type)
758
(http:bad-request "No query supplied."))))
760
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:application/json))
761
;; given a form body, decode it and extract the query
762
(let ((query (graph-store-request-query-expression resource request)))
763
(if (plusp (length query))
764
(graph-store-query resource query request response mime:application/sparql-query response-type)
765
(http:bad-request "No query supplied."))))
767
;; for construct and describe
768
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql-query) (response-type mime:rdf))
769
;; given a body with just the query text accept the entire body.
770
(let ((query (http:request-body request)))
771
(if (plusp (length query))
772
(graph-store-query resource query request response request-type response-type)
773
(http:bad-request "No query supplied."))))
775
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:text/tab-separated-values))
776
;; absent a request type, extract the query text from an url query field
777
(let ((query (graph-store-request-query-expression resource request)))
778
(if (plusp (length query))
779
(graph-store-query resource query request response mime:application/sparql-query response-type)
780
(http:bad-request "No query supplied."))))
782
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:text/csv))
783
;; absent a request type, extract the query text from an url query field
784
(let ((query (graph-store-request-query-expression resource request)))
785
(if (plusp (length query))
786
(graph-store-query resource query request response mime:application/sparql-query response-type)
787
(http:bad-request "No query supplied."))))
789
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:rdf))
790
;; given a form body, decode it and extract the query
791
(let ((query (graph-store-request-query-expression resource request)))
792
(if (plusp (length query))
793
(graph-store-query resource query request response mime:application/sparql-query response-type)
794
(http:bad-request "No query supplied."))))
796
;; for query analysis
797
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime::query))
798
;; given a body with just the query text accept the entire body and transcode iff the encoding is compatible
799
(let ((query (http:request-body request)))
800
(if (plusp (length query)) ;; do not try to get the response stream
801
(if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
802
(graph-store-query resource query request response request-type response-type)
803
(http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)." (type-of response-type)))
804
(http:bad-request "No query supplied."))))
806
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime::query))
807
;; given a body with just the query text accept the entire body and transcode iff the encoding is compatible
808
(let ((query (http:request-body request)))
809
(if (plusp (length query)) ;; do not try to get the response stream
810
(if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
811
(graph-store-query resource query request response request-type response-type)
812
(http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)." (type-of response-type)))
813
(http:bad-request "No query supplied."))))
815
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:text/csv))
816
;; absent a request type, extract the query text from an url query field
817
(let ((query (http:request-body request)))
818
(if (plusp (length query))
819
(graph-store-query resource query request response mime:application/sparql-query response-type)
820
(http:bad-request "No query supplied."))))
822
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:text/tab-separated-values))
823
;; absent a request type, extract the query text from an url query field
824
(let ((query (http:request-body request)))
825
(if (plusp (length query))
826
(graph-store-query resource query request response mime:application/sparql-query response-type)
827
(http:bad-request "No query supplied."))))
829
(:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime::query))
830
;; given a form body, decode it ans extract the query and transcode iff the encoding is compatible
831
(let ((query (graph-store-request-query-expression resource request)))
832
(if (plusp (length query))
833
(if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
834
(graph-store-query resource query request response request-type response-type)
835
(http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)." (type-of response-type)))
836
(http:bad-request "No query supplied."))))
838
(:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/sparql) (response-type t))
839
"Until view attributes are to be supported, refuse to execute a POST modify a view."
840
(http:not-implemented "View resource do not permit POST operations,"))
842
(:post ((resource |/:account/:repository/:graph|) request response (request-type mime:rdf) (response-type t))
843
(let* ((path (http:resource-path resource))
844
(location (http:request-header request "Location"))
845
(context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) path))))
846
(log-graph-store-service-response resource request location)
847
;; set the context from the mapped direct graph
848
(setf (resource-graph resource) context)
849
(labels ((post-content (pathname effective-request-type)
850
(unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
851
(conditional-delete-file pathname)))
852
(process-location (stream)
853
(setf (http:request-header request :location) nil)
854
(let ((request-content-stream (http:request-content-stream request)))
855
(unwind-protect (progn (setf (http:request-content-stream request) stream)
856
(multiple-value-call #'post-content (call-next-method)))
857
(setf (http:request-content-stream request) request-content-stream)))))
858
(cond ((is-http-url-namestring location)
859
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
860
((is-file-url-namestring location)
861
(call-with-import-file-stream #'process-location (construct-iri location)))
863
(multiple-value-call #'post-content (call-next-method)))))))
865
(:post ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
866
"Use a tarql view to decode the csv document."
867
(let* ((location (http:request-header request "Location")))
868
(log-graph-store-service-response resource request-type location)
869
(unless (http:request-query-argument request "graph")
870
;; suppress the service default which would be the resource identifier
871
(setf (resource-graph resource) nil))
872
(labels ((post-content (pathname effective-request-type)
873
;; do not impose a graph on any content
874
(unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
875
(conditional-delete-file pathname)))
876
(process-location (stream)
877
(setf (http:request-header request :location) nil)
878
(let ((request-content-stream (http:request-content-stream request)))
879
(unwind-protect (progn (setf (http:request-content-stream request) stream)
880
(multiple-value-call #'post-content (call-next-method)))
881
(setf (http:request-content-stream request) request-content-stream)))))
882
(cond ((is-http-url-namestring location)
883
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
884
((is-file-url-namestring location)
885
(call-with-import-file-stream #'process-location (construct-iri location)))
887
(multiple-value-call #'post-content (call-next-method)))))))
889
(:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
890
"Use a jarql view to decode the json document.
891
This behaves analogous to tarql, but for json content"
892
(let* ((location (http:request-header request "Location")))
893
(log-graph-store-service-response resource request-type location)
894
(unless (http:request-query-argument request "graph")
895
;; suppress the service default which would be the resource identifier
896
(setf (resource-graph resource) nil))
897
(labels ((post-content (pathname effective-request-type)
898
;; do not impose a graph on any content
899
(unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
900
(conditional-delete-file pathname)))
901
(process-location (stream)
902
(setf (http:request-header request :location) nil)
903
(let ((request-content-stream (http:request-content-stream request)))
904
(unwind-protect (progn (setf (http:request-content-stream request) stream)
905
(multiple-value-call #'post-content (call-next-method)))
906
(setf (http:request-content-stream request) request-content-stream)))))
907
(cond ((is-http-url-namestring location)
908
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
909
((is-file-url-namestring location)
910
(call-with-import-file-stream #'process-location (construct-iri location)))
912
;; allow to decompress
913
(multiple-value-call #'post-content (call-next-method)))))))
915
(:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
916
"Use a jarql view to decode the json document.
917
This behaves analogous to tarql, but for json content"
918
(let* ((location (http:request-header request "Location")))
919
(log-graph-store-service-response resource request-type location)
920
(unless (http:request-query-argument request "graph")
921
;; suppress the service default which would be the resource identifier
922
(setf (resource-graph resource) nil))
923
(labels ((post-content (pathname effective-request-type)
924
;; do not impose a graph on any content
925
(unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
926
(conditional-delete-file pathname)))
927
(process-location (stream)
928
(setf (http:request-header request :location) nil)
929
(let ((request-content-stream (http:request-content-stream request)))
930
(unwind-protect (progn (setf (http:request-content-stream request) stream)
931
(multiple-value-call #'post-content (call-next-method)))
932
(setf (http:request-content-stream request) request-content-stream)))))
933
(cond ((is-http-url-namestring location)
934
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
935
((is-file-url-namestring location)
936
(call-with-import-file-stream #'process-location (construct-iri location)))
938
;; allow to decompress
939
(multiple-value-call #'post-content (call-next-method)))))))
941
;;; do _not_ specialize the request type - allow the decode methods to handle the combinations
942
;;; otherwise the combination with text/plain methods are not correct
943
(:put ((resource |/:account/:repository/service|) request response (request-type t) (response-type t))
944
"Treat text/plain content as if it were ntriples - that is, use it directly"
945
(let* ((location (http:request-header request "Location")))
946
(log-graph-store-service-response resource request location)
947
;; given a location, take the content from there.
948
;; otherwise decode it from the request stream.
949
(labels ((put-content (pathname effective-request-type)
950
;; do not impose a graph on any content
951
(unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
952
(conditional-delete-file pathname)))
953
(process-location (stream)
954
(setf (http:request-header request :location) nil)
955
(let ((request-content-stream (http:request-content-stream request)))
956
(unwind-protect (progn (setf (http:request-content-stream request) stream)
957
(multiple-value-call #'put-content (call-next-method)))
958
(setf (http:request-content-stream request) request-content-stream)))))
959
(cond ((is-http-url-namestring location)
960
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
961
((is-file-url-namestring location)
962
(call-with-import-file-stream #'process-location (construct-iri location)))
964
(multiple-value-call #'put-content (call-next-method)))))))
966
;;; distinguish import from view modification based on content type
967
(:put ((resource |/:account/:repository/:graph|) request response (request-type mime:rdf) (response-type t))
968
"map the graph to the abstract resource identifier and proceed with that"
969
(let* ((path (http:resource-path resource))
970
(context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) path)))
971
(location (http:request-header request "Location")))
972
(log-graph-store-service-response resource request-type location)
973
(setf (resource-graph resource) context)
974
(labels ((put-content (pathname effective-request-type)
975
;; do not impose a graph on any content
976
(unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
977
(conditional-delete-file pathname)))
978
(process-location (stream)
979
(setf (http:request-header request :location) nil)
980
(let ((request-content-stream (http:request-content-stream request)))
981
(unwind-protect (progn (setf (http:request-content-stream request) stream)
982
(multiple-value-call #'put-content (call-next-method)))
983
(setf (http:request-content-stream request) request-content-stream)))))
984
(cond ((is-http-url-namestring location)
985
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
986
((is-file-url-namestring location)
987
(call-with-import-file-stream #'process-location (construct-iri location)))
989
(multiple-value-call #'put-content (call-next-method)))))))
991
(:put ((resource |/:account/:repository/:graph|) request response (request-type mime:application/sparql) (response-type t))
992
"Create or modify a view.
993
determine the response media type from the resource file type"
994
(let ((query (http:request-body request)))
995
(if (plusp (length query))
996
(graph-store-put-view resource request response query)
997
(http:bad-request "No query supplied."))))
999
(:put ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
1000
"Use a tarql view to decode the csv document"
1001
(let* ((location (http:request-header request "Location")))
1002
(log-graph-store-service-response resource request-type location)
1003
(unless (http:request-query-argument request "graph")
1004
;; suppress the service default which would be the resource identifier
1005
(setf (resource-graph resource) nil))
1006
(labels ((put-content (pathname effective-request-type)
1007
;; do not impose a graph on any content
1008
(unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
1009
(conditional-delete-file pathname)))
1010
(process-location (stream)
1011
(setf (http:request-header request :location) nil)
1012
(let ((request-content-stream (http:request-content-stream request)))
1013
(unwind-protect (progn (setf (http:request-content-stream request) stream)
1014
(multiple-value-call #'put-content (call-next-method)))
1015
(setf (http:request-content-stream request) request-content-stream)))))
1016
(cond ((is-http-url-namestring location)
1017
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
1018
((is-file-url-namestring location)
1019
(call-with-import-file-stream #'process-location (construct-iri location)))
1021
(multiple-value-call #'put-content (call-next-method)))))))
1023
(:put ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
1024
"Use a jarql view to decode the json document"
1025
(let* ((location (http:request-header request "Location")))
1026
(log-graph-store-service-response resource request-type location)
1027
(unless (http:request-query-argument request "graph")
1028
;; suppress the service default which would be the resource identifier
1029
(setf (resource-graph resource) nil))
1030
(labels ((put-content (pathname effective-request-type)
1031
;; do not impose a graph on any content
1032
(unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
1033
(conditional-delete-file pathname)))
1034
(process-location (stream)
1035
(setf (http:request-header request :location) nil)
1036
(let ((request-content-stream (http:request-content-stream request)))
1037
(unwind-protect (progn (setf (http:request-content-stream request) stream)
1038
(multiple-value-call #'put-content (call-next-method)))
1039
(setf (http:request-content-stream request) request-content-stream)))))
1040
(cond ((is-http-url-namestring location)
1041
(hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
1042
((is-file-url-namestring location)
1043
(call-with-import-file-stream #'process-location (construct-iri location)))
1045
(multiple-value-call #'put-content (call-next-method)))))))
1047
(:delete ((resource |/:account/:repository/service|) request response request-type response-type)
1048
(graph-store-delete-graph resource request response))
1050
(:delete ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type t))
1051
"Create or modify a view.
1052
determine the response media type from the resource file type"
1053
(graph-store-delete-view resource request))
1055
(:decode ((resource graph-store-service-resource) request response (request-type mime:rdf) (response-type t))
1056
"Perform the base receive of rdf request data into a file and return the pathname to
1057
be used directly or to be trancoded."
1058
(let* ((repository (resource-repository resource))
1059
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1060
(dydra:repository repository))))
1061
(copy-request-content request pathname)
1062
(values pathname request-type)))
1064
(:decode ((resource graph-store-service-resource) request response (request-type mime:multipart/*) (response-type t))
1065
"Perform the base receive of the request data into a file and return the pathname to
1066
be used directly or to be trancoded."
1067
(let* ((repository (resource-repository resource))
1068
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1069
(dydra:repository repository))))
1070
(copy-request-content request pathname)
1071
(values pathname request-type)))
1073
(:decode ((resource graph-store-service-resource) request response (request-type mime:application/trix) (response-type t))
1074
"translate trix into nquads"
1075
(let* ((repository (resource-repository resource))
1076
(pathname (call-next-method))
1077
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1078
(dydra:repository repository)))
1079
(process (unwind-protect (run-program dydra:*executable-pathname.trix2nq* ()
1080
:input pathname :output nq-pathname
1082
(conditional-delete-file pathname))))
1083
(unless (and process (zerop (run-program-exit-code process)))
1084
(conditional-delete-file nq-pathname)
1085
(case (when process (run-program-exit-code process))
1086
(1 (http:bad-request "trix to nquad conversion failed."))
1087
(t (http:internal-error "trix to nquad conversion failed."))))
1088
(when process (run-program-close process))
1089
(values nq-pathname mime:application/n-quads)))
1091
(:decode ((resource graph-store-service-resource) request response (request-type mime:application/rdf+json) (response-type t))
1092
"Transate rdf+json into nquads.
1093
NB. 20170720, the dydra-import stubs this media type out to an error, so this path uses rapper to translate."
1094
(let* ((repository (resource-repository resource))
1095
(pathname (call-next-method))
1096
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1097
(dydra:repository repository)))
1098
(base-iri (http:request-base-iri request))
1099
(process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1100
`("-q" "-i" "json" "-o" "ntriples"
1101
,(namestring (truename pathname))
1103
:environment () ;; isolate rapper from dydra libraries
1106
(conditional-delete-file pathname))))
1107
(unless (and process (zerop (run-program-exit-code process)))
1108
(conditional-delete-file nq-pathname)
1109
(case (when process (run-program-exit-code process))
1110
(1 (http:bad-request "rdf+json to n-triples conversion failed."))
1111
(t (http:internal-error "rdf+json to n-triples conversion failed."))))
1112
(when process (run-program-close process))
1113
(values nq-pathname mime:application/n-triples base-iri)))
1115
(:decode ((resource graph-store-service-resource) request response (request-type mime:text/turtle) (response-type t))
1116
"transate turtle into nquads w/base-iri"
1117
(let* ((repository (resource-repository resource))
1118
(pathname (call-next-method))
1119
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1120
(dydra:repository repository)))
1121
(base-iri (http:request-base-iri request))
1122
(process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1123
`("-q" "-i" "turtle" "-o" "ntriples"
1124
,(namestring (truename pathname))
1126
:environment () ;; isolate rapper from dydra libraries
1129
(conditional-delete-file pathname))))
1130
(unless (and process (zerop (run-program-exit-code process)))
1131
(conditional-delete-file nq-pathname)
1132
(case (when process (run-program-exit-code process))
1133
(1 (http:bad-request "turtle to n-triples conversion failed."))
1134
(t (http:internal-error "turtle to n-triples conversion failed."))))
1135
(when process (run-program-close process))
1136
(values nq-pathname mime:application/n-triples)))
1140
(:decode ((resource graph-store-service-resource) request response (request-type mime:application/ld+json) (response-type t))
1141
"transate turtle into nquads w/base-iri"
1142
(let* ((repository (resource-repository resource))
1143
(pathname (call-next-method))
1144
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1145
(dydra:repository repository)))
1146
(base-iri (http:request-base-iri request))
1147
(process (unwind-protect (run-program dydra:*executable-pathname.jsonld* `(,*output-format.jsonld*
1149
"--uri" ,(dydra:iri-lexical-form base-iri)
1150
,(namestring (truename pathname)))
1151
:environment () ;; isolate from dydra libraries
1154
(conditional-delete-file pathname))))
1155
(unless (and process (zerop (run-program-exit-code process)))
1156
(conditional-delete-file nq-pathname)
1157
(case (when process (run-program-exit-code process))
1158
(1 (http:bad-request "json-ld to n-triples conversion failed."))
1159
(t (http:internal-error "json-ld to n-triples conversion failed."))))
1160
(when process (run-program-close process))
1161
(values nq-pathname mime:application/n-quads)))
1163
(:decode ((resource graph-store-service-resource) request response (request-type mime:application/trig) (response-type t))
1164
"transate turtle into nquads w/base-iri"
1165
(let* ((repository (resource-repository resource))
1166
(pathname (call-next-method))
1167
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1168
(dydra:repository repository)))
1169
(process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1170
`("-q" "-i" "trig" "-o" "nquads"
1171
,(namestring (truename pathname))
1173
:environment () ;; isolate rapper from dydra libraries
1176
(conditional-delete-file pathname))))
1177
(unless (and process (zerop (run-program-exit-code process)))
1178
(conditional-delete-file nq-pathname)
1179
(case (when process (run-program-exit-code process))
1180
(1 (http:bad-request "trig to n-quad conversion failed."))
1181
(t (http:internal-error "trig to n-quad conversion failed."))))
1182
(when process (run-program-close process))
1183
(values nq-pathname mime:application/n-quads)))
1185
(:decode ((resource graph-store-service-resource) request response (request-type mime:application/xhtml+xml) (response-type t))
1186
"transate application/xhtml+xml as rdfa into nquads. allows for non-standard 'application/xhtml+rdfa'.
1187
(see http://www.w3.org/TR/rdfa-in-html/)"
1188
(let* ((repository (resource-repository resource))
1189
(pathname (call-next-method))
1190
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1191
(dydra:repository repository)))
1192
(process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1193
`("-q" "-i" "rdfa" "-o" "ntriples" ,(namestring (truename pathname)) "-")
1194
:environment () ;; isolate rapper from dydra libraries
1197
(conditional-delete-file pathname))))
1198
(unless (and process (zerop (run-program-exit-code process)))
1199
(conditional-delete-file nq-pathname)
1200
(case (when process (run-program-exit-code process))
1201
(1 (http:bad-request "xhtml+rdfa to n-triples conversion failed."))
1202
(t (http:internal-error "xhtml+rdfa to n-triples conversion failed."))))
1203
(when process (run-program-close process))
1204
(values nq-pathname mime:application/n-triples)))
1206
(:decode ((resource graph-store-service-resource) request response (request-type mime:text/html) (response-type t))
1207
"transate text/html as rdfa into nquads. allows for non-standard 'text/html+rdfa'.
1208
(see http://www.w3.org/TR/rdfa-in-html/)"
1209
(let* ((repository (resource-repository resource))
1210
(pathname (call-next-method))
1211
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1212
(dydra:repository repository)))
1213
(process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1214
`("-q" "-i" "rdfa" "-o" "ntriples" ,(namestring (truename pathname)) "-")
1215
:environment () ;; isolate rapper from dydra libraries
1218
(conditional-delete-file pathname))))
1219
(unless (and process (zerop (run-program-exit-code process)))
1220
(conditional-delete-file nq-pathname)
1221
(case (when process (run-program-exit-code process))
1222
(1 (http:bad-request "html+rdfa to n-triples conversion failed."))
1223
(t (http:internal-error "html+rdfa to n-triples conversion failed."))))
1224
(when process (run-program-close process))
1225
(values nq-pathname mime:application/n-triples)))
1227
(:decode ((resource graph-store-service-resource) request response (request-type mime:text/plain) (response-type t))
1228
"Perform the receive of text/plain request data into a file and return the pathname to
1229
be used directly as n-triples"
1230
(let* ((repository (resource-repository resource))
1231
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1232
(dydra:repository repository))))
1233
(copy-request-content request pathname)
1234
(values pathname request-type)))
1236
(:decode ((resource graph-store-binary-resource) request response (request-type t) (response-type t))
1237
"If the resource support indiscriminate types, receive the request data into a file and return the pathname to
1238
be used directly or to be transcoded."
1239
(let* ((repository (resource-repository resource))
1240
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1241
(dydra:repository repository)))
1242
(content-length (http:request-content-length request)))
1243
(cond ((and content-length (plusp content-length))
1244
(copy-request-content request pathname)
1245
(values pathname request-type))
1247
;; allow for no content
1248
(values nil request-type)))))
1250
(:decode ((resource graph-store-service-resource) request response (request-type t) (response-type t))
1251
"Perform the base receive of arbitrary request data into a file and return the pathname to
1252
be used directly or to be transcoded.
1253
Recognize any content encoding. Of those only gzip is processed"
1254
(let* ((repository (resource-repository resource))
1255
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1256
(dydra:repository repository))))
1257
(handler-case (copy-request-content request pathname)
1259
(http:bad-request "Incomplete request content: ~a" c)))
1260
(values pathname request-type)))
1262
(:decode ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
1263
"Use a tarql view to decode the csv document."
1264
(let* ((repository (resource-repository resource))
1265
(view-name (or (http:resource-path-name-and-type resource)
1266
(http:bad-request "No view spaeified")))
1267
(view (spocq.i::make-view :repository (resource-repository resource) :name view-name))
1268
(view-text (cond ((spocq.i::read-view-definition view)
1269
(if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
1270
(spocq.i::view-query view)
1271
(http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
1274
(view-query (parse-sparql view-text))
1275
(pathname (call-next-method))
1276
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1277
(dydra:repository repository)))
1278
(variables (mapcar #'(lambda (name) (intern (spocq.i::csv-variable-name name) *variable-package*))
1279
(spocq.i::parse-csv-header (or (http:request-header request :csv-header)
1280
(with-open-file (stream pathname :direction :input)
1281
(handler-case (read-line stream)
1283
(log-warn "Incomplete request content: no header: ~a: ~a"
1289
(http:bad-request "Incomplete request content: no header: ~a" resource))
1290
(let ((table-count 0))
1291
;; rewrite the query expression to replace each table form with a
1292
;; reference to the csv source. each will then read it and feed the
1293
;; content into the algebra reduction process.
1294
;; emit the output to a file and return its pathname as the effective source for the request.
1295
(flet ((replace-table (op)
1296
(cond ((spocq.i::table-form-p op)
1298
`(spocq.a::|csv-bindings| ,pathname ,variables))
1299
((and (spocq.i::select-form-p op) (spocq.i::table-form-p (second op)))
1301
`(spocq.a::|csv-bindings| ,pathname ,(third op)))
1304
(declare (dynamic-extent #'replace-table))
1305
(setf view-query (map-tree #'replace-table view-query)))
1306
(unless (or (spocq.i::construct-form-p view-query) (spocq.i::describe-form-p view-query))
1307
(http:bad-request "Invalid tarql view:~%~s" view-text))
1308
(when (zerop table-count)
1309
(http:bad-request "Invalid tarql view:~%~s" view-text)))
1311
(handler-case (with-open-file (nq-stream nq-pathname :direction :output :if-does-not-exist :create :if-exists :supersede)
1312
(spocq.i::pipe-query view-query nq-stream
1313
:repository-id (repository-id repository)
1314
:response-content-type mime:application/n-quads
1315
:agent (spocq.i::system-agent)))
1317
(conditional-delete-file nq-pathname)
1318
(http:bad-request "csv to n-triples conversion failed: ~a" c)))
1320
(conditional-delete-file pathname))
1321
;; (print (list nq-pathname (spocq.i::read-file nq-pathname)))
1322
(values nq-pathname mime:application/n-triples)))
1324
(:decode ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
1325
"Use a jarql view to decode the json document."
1326
(let* ((repository (resource-repository resource))
1327
(view-name (or (http:resource-path-name-and-type resource)
1328
(http:bad-request "No view spaeified")))
1329
(view (spocq.i::make-view :repository (resource-repository resource) :name view-name))
1330
(view-text (cond ((spocq.i::read-view-definition view)
1331
(if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
1332
(spocq.i::view-query view)
1333
(http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
1336
(view-query (parse-sparql view-text))
1337
(pathname (call-next-method))
1338
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1339
(dydra:repository repository)))
1340
(dimensions (spocq.i::expression-dimensions view-query)))
1341
(let ((table-count 0))
1342
;; rewrite the query expression to replace each table form with a
1343
;; reference to the csv source. each will then read it and feed the
1344
;; content into the algebra reduction process.
1345
;; emit the output to a file and return its pathname as the effective source for the request.
1346
(flet ((replace-table (op)
1347
(cond ((spocq.i::table-form-p op)
1349
;; default to all variables in the query
1350
`(spocq.a::|json-bindings| ,pathname ,dimensions))
1351
((and (spocq.i::select-form-p op) (spocq.i::table-form-p (second op)))
1353
`(spocq.a::|json-bindings| ,pathname ,(third op)))
1356
(declare (dynamic-extent #'replace-table))
1357
(setf view-query (map-tree #'replace-table view-query)))
1358
(unless (or (spocq.i::construct-form-p view-query) (spocq.i::describe-form-p view-query))
1359
(http:bad-request "Invalid jarql view:~%~s" view-text))
1360
(when (zerop table-count)
1361
(http:bad-request "Invalid jarql view:~%~s" view-text)))
1363
(handler-case (with-open-file (nq-stream nq-pathname :direction :output :if-does-not-exist :create :if-exists :supersede)
1364
(spocq.i::pipe-query view-query nq-stream
1365
:repository-id (repository-id repository)
1366
:response-content-type mime:application/n-quads
1367
:agent (spocq.i::system-agent)))
1369
(conditional-delete-file nq-pathname)
1370
(http:bad-request "json to n-triples conversion failed: ~a" c)))
1372
(conditional-delete-file pathname))
1373
;; (print (list nq-pathname (spocq.i::read-file nq-pathname)))
1374
(values nq-pathname mime:application/n-triples)))
1378
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/json))
1379
(let ((query (http:request-query-argument request "query")))
1380
(if (plusp (length query))
1381
(graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1382
(http:bad-request "The request must include a query."))))
1384
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:graphviz))
1385
(let ((query (http:request-query-argument request "query")))
1387
(graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1388
(http:bad-request "The request must include a query."))))
1390
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:text/csv))
1391
(let ((query (http:request-query-argument request "query")))
1392
(if (plusp (length query))
1393
(graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1394
(http:bad-request "The request must include a query."))))
1396
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:text/tab-separated-values))
1397
(let ((query (http:request-query-argument request "query")))
1398
(if (plusp (length query))
1399
(graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1400
(http:bad-request "The request must include a query."))))
1402
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/sql))
1403
;; absent a request type, extract the query text from an url query field
1404
(let ((query (http:request-query-argument request "query")))
1405
(if (plusp (length query))
1406
(graph-store-query resource query request response mime:application/sparql-query response-type)
1407
(http:bad-request "The request must include a query."))))
1409
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/vnd.dydra.sparql-query))
1410
;; absent a request type, extract the query text from an url query field
1411
(let ((query (http:request-query-argument request "query")))
1412
(if (plusp (length query))
1413
(compute-get-query-execution-graph resource query request response mime:application/sparql-query (http:response-media-type response))
1414
(http:bad-request "The request must include a query."))))
1417
(:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type t))
1418
;; absent a request type, extract the query text from an url query field
1419
;; then, absetnt the response type, attempt to derive it from the resource-name
1420
(let ((query (http:request-query-argument request "query")))
1421
(if (plusp (length query))
1422
(compute-get-query-execution-graph resource query request response mime:application/sparql-query (http:response-media-type response))
1423
(http:bad-request "The request must include a query."))))
1425
(:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type mime:rdf))
1426
"Respond with graph content."
1427
(let* ((context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) (http:resource-path resource))))
1428
(service (or (http:request-header request :dydra_service) "service")))
1429
(cond ((string-equal service "service")
1430
(graph-store-get-graph resource request response request-type response-type
1432
((string-equal service "ldp")
1433
;; respond to a ldp request w/o content
1434
(linked-data-platform-response :get resource request response request-type response-type nil))
1436
(http:not-found)))))
1438
;;; recording, tracing, monitoring, ...
1440
(defun log-graph-store-service-response (resource request location)
1441
(dydra:log-info "graph-store-response.service ~a ~a ~a"
1442
resource request location))
1444
(defun trace-graph-store-response (resource request response content-type accept)
1445
(when *graph-store-response.print-methods*
1446
(let ((*print-pretty* nil))
1447
(map nil 'print (compute-applicable-methods #'spocq.si::graph-store-response
1448
(list resource request response content-type accept)))
1450
(map nil #'print (http:request-headers request)))))
1452
#+(or) ;; cause effective method recomputation
1453
(defmethod graph-store-response
1455
((resource t) (request t) (response t) (request-type mime:mime-type) (response-type mime:mime-type))
1461
(defmethod http:stream-header-stream ((stream broadcast-stream))
1462
(make-broadcast-stream (http:stream-header-stream (first (broadcast-stream-streams stream)))
1463
(second (broadcast-stream-streams stream))))
1465
(defmethod DE.SETF.HTTP.IMPLEMENTATION::STREAM-CLEAR-HEADER-OUTPUT ((stream broadcast-stream))
1466
(DE.SETF.HTTP.IMPLEMENTATION::STREAM-CLEAR-HEADER-OUTPUT (first (broadcast-stream-streams stream))))
1468
(defmethod (setf DE.SETF.HTTP:STREAM-MEDIA-TYPE) (type (stream broadcast-stream))
1469
(setf (DE.SETF.HTTP:STREAM-MEDIA-TYPE (first (broadcast-stream-streams stream))) type))
1471
(defmethod (setf CHUNGA:CHUNKED-STREAM-OUTPUT-CHUNKING-P) (value (stream broadcast-stream))
1472
(setf (CHUNGA:CHUNKED-STREAM-OUTPUT-CHUNKING-P (first (broadcast-stream-streams stream))) value))
1474
(defmethod http:copy-stream ((stream echo-stream) (output string) &rest args)
1475
(prog1 (apply #'http:copy-stream (echo-stream-input-stream stream) output args)
1476
(write-string output (echo-stream-output-stream stream))))
1478
(defgeneric copy-request-content (request pathname &key content-length content-encoding)
1479
(:method ((request http:request) (pathname pathname) &key
1480
(content-length (http:request-content-length request))
1481
(content-encoding (http:request-header request "Content-Encoding")))
1482
(let ((import-limit (spocq.e:import-limit)))
1484
(unless (<= content-length import-limit)
1485
(http:request-entity-too-large "Content exceeds length limit: ~s." import-limit))
1486
(setf content-length import-limit))
1487
(case (typecase content-encoding
1488
(symbol content-encoding)
1489
(string (find-symbol (string-upcase content-encoding) :keyword))
1490
(t (http:unsupported-media-type "Unsupported content encoding: ~a" content-encoding)))
1492
(http:copy-stream (http:request-content-stream request) pathname :length content-length)
1495
(let ((exit-code -1)
1496
(gzip-input-pathname (tmp-import-pathname "gzip")))
1497
(http:copy-stream (http:request-content-stream request) gzip-input-pathname :length content-length)
1499
(with-open-file (output pathname :direction :output
1500
:element-type '(unsigned-byte 8)
1501
:if-does-not-exist :create)
1502
;; (describe (http:request-content-stream request))
1503
(let ((process (unwind-protect (run-program "/bin/gunzip" ;; dydra:*executable-pathname.zip*
1504
`("-c" ,(namestring (truename gzip-input-pathname)))
1505
:input nil ;(http:request-content-stream request)
1507
:environment () ;; isolate from dydra libraries
1510
(case (run-program-exit-code process)
1515
(log-warn "Content decoding failed: ~s" (run-program-exit-code process))
1516
(http:bad-request "Content decoding failed: ~s" (run-program-exit-code process)))))
1518
(http:internal-error "Content decoding failed: no process")))))
1519
(conditional-delete-file gzip-input-pathname)
1520
(unless (zerop exit-code)
1521
(conditional-delete-file pathname)))))))))