Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Download of Binary Resources #2119

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/scripts/install-xq.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash -e

sudo apt-get -y install xq
20 changes: 20 additions & 0 deletions .github/scripts/read-binary-content-not-found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash -e

# This script queries the server for a non-existent binary resource
# and verifies that we get the 404 error message.

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
. "$SCRIPT_DIR/util.sh"

BASE="http://localhost:8080/fhir"

RANDOM_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"

# Attempt to retrieve the Binary resource by ID
echo "Verifying that the Binary resource with ID '$RANDOM_ID' does not exist."

# Perform a GET request to retrieve the Binary resource by ID
STATUS_CODE=$(curl -s -H "Accept: application/pdf" -o /dev/null -w '%{response_code}' "$BASE/Binary/$RANDOM_ID")

# Test that the response code is 404 (Not Found), indicating the resource doesn't exist
test "GET response code for Binary resource" "$STATUS_CODE" "404"
39 changes: 39 additions & 0 deletions .github/scripts/read-binary-content-via-json-found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash -e

# This script creates a binary resource and verifies that its binary content
# can be read (via JSON).

BASE="http://localhost:8080/fhir"

# 10 KiB of random data, base64 encoded
DATA="$(openssl rand -base64 10240 | tr -d '\n')"

binary() {
cat <<END
{
"resourceType": "Binary",
"contentType": "application/pdf",
"data": "$DATA"
}
END
}

# Create a Binary resource that contains that data, and get its ID (via JSON)
ID_VIA_JSON=$(curl -s -H 'Content-Type: application/fhir+json' -d "$(binary)" "$BASE/Binary" | jq -r '.id')

echo "Created Binary resource that contains the Random Data"
echo " - via JSON, with ID: $ID_VIA_JSON"


# Retrieve the Binary resource, and Base64 encode it so it can be safely handled by Bash (JSON)
BASE64_ENCODED_BINARY_RESOURCE_VIA_JSON=$(curl -s -H 'Accept: application/pdf' "$BASE/Binary/$ID_VIA_JSON" | base64 | tr -d '\n')


echo "Binary data retrieved. Verifying content... (JSON version)"

if [ "$DATA" = "$BASE64_ENCODED_BINARY_RESOURCE_VIA_JSON" ]; then
echo "✅ Base64 encoding of both the Original Data and the Retrieved Resource Data match (JSON)"
else
echo "🆘 Base64 encoding of both the Original Data and the Retrieved Resource Data are different (JSON)"
exit 1
fi
39 changes: 39 additions & 0 deletions .github/scripts/read-binary-content-via-xml-found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash -e

# This script creates a binary resource and verifies that its binary content
# can be read (via XML).

BASE="http://localhost:8080/fhir"

# 10 KiB of random data, base64 encoded
DATA="$(openssl rand -base64 10240 | tr -d '\n')"

binary() {
cat <<END
<Binary xmlns="http://hl7.org/fhir">
<contentType value="application/pdf"/>
<data value="$DATA"/>
</Binary>
END
}


# Create a Binary resource that contains that data, and get its ID (via XML)
ID_VIA_XML=$(curl -s -H 'Content-Type: application/fhir+xml' -H 'Accept: application/fhir+xml' -d "$(binary)" "$BASE/Binary" | xq -x //id/@value)

echo "Created Binary resource that contains the Random Data"
echo " - via XML, with ID: $ID_VIA_XML"


# Retrieve the Binary resource, and Base64 encode it so it can be safely handled by Bash (via XML)
BASE64_ENCODED_BINARY_RESOURCE_VIA_XML=$(curl -s -H 'Accept: application/pdf' "$BASE/Binary/$ID_VIA_XML" | base64 | tr -d '\n')


echo "Binary data retrieved. Verifying content... (XML version)"

if [ "$DATA" = "$BASE64_ENCODED_BINARY_RESOURCE_VIA_XML" ]; then
echo "✅ Base64 encoding of both the Original Data and the Retrieved Resource Data match (XML)"
else
echo "🆘 Base64 encoding of both the Original Data and the Retrieved Resource Data are different (XML)"
exit 1
fi
12 changes: 12 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,9 @@ jobs:
- name: Install Blazectl
run: .github/scripts/install-blazectl.sh

- name: Install xq
run: .github/scripts/install-xq.sh

- name: Download Blaze Image
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
Expand Down Expand Up @@ -1059,6 +1062,15 @@ jobs:
- name: Search _tag
run: .github/scripts/search-tag.sh

- name: Binary Content Download - not found
run: .github/scripts/read-binary-content-not-found.sh

- name: Binary Content Download - found (via JSON)
run: .github/scripts/read-binary-content-via-json-found.sh

- name: Binary Content Download - found (via XML)
run: .github/scripts/read-binary-content-via-xml-found.sh

- name: Conditional Delete - Check Referential Integrity Violated
run: .github/scripts/conditional-delete-type/check-referential-integrity-violated.sh

Expand Down
3 changes: 3 additions & 0 deletions modules/rest-api/src/blaze/rest_api/routes.clj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
:compile (fn [{:keys [response-type] :response-type.json/keys [opts]} _]
(condp = response-type
:json (output/wrap-output opts)
:binary fhir-output/wrap-binary-output
:none identity
fhir-output/wrap-output))})

Expand Down Expand Up @@ -169,6 +170,8 @@
(cond->
{:name (keyword name "instance")
:conflicting true}
(= name "Binary")
(assoc :response-type :binary)
(contains? interactions :read)
(assoc :get {:interaction "read"
:middleware [[wrap-db node db-sync-timeout]]
Expand Down
2 changes: 1 addition & 1 deletion modules/rest-api/src/blaze/rest_api/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
boolean?)

(s/def ::operation/response-type
#{:json})
#{:json :binary})

(s/def ::operation/resource-types
(s/coll-of string?))
Expand Down
55 changes: 47 additions & 8 deletions modules/rest-util/src/blaze/middleware/fhir/output.clj
allentiak marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
(:require
[blaze.anomaly :as ba]
[blaze.fhir.spec :as fhir-spec]
[blaze.fhir.spec.type :as type]
[blaze.handler.util :as handler-util]
[clojure.data.xml :as xml]
[clojure.java.io :as io]
Expand All @@ -15,7 +16,8 @@
[ring.util.response :as ring]
[taoensso.timbre :as log])
(:import
[java.io ByteArrayOutputStream]))
[java.io ByteArrayOutputStream]
[java.util Base64]))

(set! *warn-on-reflection* true)

Expand All @@ -27,6 +29,12 @@

(def ^:private parse-accept (parse/fast-memoize 1000 parse/parse-accept))

(defn- generate-error [generation-fn ex]
(-> ex
ba/anomaly
handler-util/operation-outcome
generation-fn))

(defn- generate-json [body]
(log/trace "generate JSON")
(with-open [_ (prom/timer generate-duration-seconds "json")]
Expand All @@ -38,25 +46,36 @@
(xml/emit (fhir-spec/unform-xml body) writer))
(.toByteArray out)))

(defn- generate-xml-error [e]
(-> e
ba/anomaly
handler-util/operation-outcome
generate-xml**))

(defn- generate-xml* [response]
(try
(update response :body generate-xml**)
(catch Throwable e
(assoc response
:body (generate-xml-error e)
:body (generate-error generate-xml** e)
:status 500))))

(defn- generate-xml [response]
(log/trace "generate XML")
(with-open [_ (prom/timer generate-duration-seconds "xml")]
(generate-xml* response)))

(defn- generate-binary** [body]
(when (:data body)
(.decode (Base64/getDecoder) ^String (type/value (:data body)))))

(defn- generate-binary* [response]
(try
(update response :body generate-binary**)
(catch Throwable e
(assoc response
:body (generate-error generate-binary** e)
:status 500))))

(defn- generate-binary [response]
(log/trace "generate binary")
(with-open [_ (prom/timer generate-duration-seconds "binary")]
(generate-binary* response)))

(defn- encode-response-json [{:keys [body] :as response} content-type]
(cond-> response body (-> (update :body generate-json)
(ring/content-type content-type))))
Expand All @@ -65,6 +84,14 @@
(cond-> response body (-> generate-xml
(ring/content-type content-type))))

(defn- binary-content-type [body]
(or (-> body :contentType type/value)
"application/octet-stream"))

(defn- encode-response-binary [{:keys [body] :as response}]
(cond-> response body (-> generate-binary
(ring/content-type (binary-content-type body)))))

(defn- format-key [format]
(condp = format
"application/fhir+json" :fhir+json
Expand Down Expand Up @@ -104,3 +131,15 @@
([handler opts]
(fn [request respond raise]
(handler request #(respond (handle-response opts request %)) raise))))

(defn handle-binary-response [request response]
(case (request-format request)
:fhir+json (encode-response-json response "application/fhir+json;charset=utf-8")
:fhir+xml (encode-response-xml response "application/fhir+xml;charset=utf-8")
(encode-response-binary response)))

(defn wrap-binary-output
allentiak marked this conversation as resolved.
Show resolved Hide resolved
"Middleware to output binary resources."
[handler]
(fn [request respond raise]
(handler request #(respond (handle-binary-response request %)) raise)))
Loading
Loading