FileMaker Data APIでオブジェクトフィールドのデータをダウンロードする方法

公開日: 更新日:

FileMaker Data APIを使用して、オブジェクトフィールドのデータをダウンロードする要件があり、その方法を調査しました。

仕組みを確認したかったため、備忘録として情報を整理してまとめておきます。

環境

  • FileMaker Server 20.3.1.31
  • macOS Ventura
  • curl 8.4.0

準備

FileMaker Serverに、テストのため、最小限のファイルをアップロードしました。

レコードIDオブジェクトフィールドという2つのフィールドのみです。

サンプルファイル(blank.fmp12)
サンプルファイル(blank.fmp12)

手順

具体的なcurlコマンドと、その実行結果(レスポンス)を順を追って解説します。

Loginメソッドで、セッショントークンを取得

FileMaker Data APIのドキュメントの通り、Loginメソッドを実行します。

リクエスト

TOKEN=$(echo -n "Administrator:password" | base64)

curl --request "POST" \
  -H "Authorization: Basic ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{}" \
  --include \
  "https://fm.example.com/fmi/data/v2/databases/blank/sessions" > "$(date "+%Y%m%d_%H%M%S").txt"
Loginメソッドの実行(セッショントークン取得)

レスポンス(HEADER + JSON)

HTTP/2 200
server: nginx
date: Fri, 29 Mar 2024 17:19:08 GMT
content-type: application/json; charset=utf-8
content-length: 118
cache-control: no-cache
x-fm-data-access-token: 6ae7a50a4ad5dd96fe2c3d3237017ce8c263cb176dbb1098fbbe
etag: W/"76-vNMv3Nvh+NE0GQhO8vabG+K3igs"
vary: Accept-Encoding
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
access-control-allow-origin: instance-5-pt9
access-control-allow-credentials: True
access-control-allow-headers: Content-Type,Authorization

{"response":{"token":"6ae7a50a4ad5dd96fe2c3d3237017ce8c263cb176dbb1098fbbe"},"messages":[{"code":"0","message":"OK"}]}
Loginレスポンス(Header + JSON)

レスポンス(JSON)

{
  "response": {
    "token": "6ae7a50a4ad5dd96fe2c3d3237017ce8c263cb176dbb1098fbbe"
  },
  "messages": [
    {
      "code": "0",
      "message": "OK"
    }
  ]
}
Loginレスポンス(JSONのみ抜粋)

Findメソッドで、該当のレコードを検索

FileMaker Data APIのドキュメントの通り、Findメソッドを実行します。

リクエスト

SESSION_TOKEN="6ae7a50a4ad5dd96fe2c3d3237017ce8c263cb176dbb1098fbbe"

DATA='{
  "query": [
    {
      "レコードID": 1
    }
  ]
}'

curl --request "POST" \
  -H "Authorization: Bearer ${SESSION_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "${DATA}" \
  --include \
  "https://fm.example.com/fmi/data/v2/databases/blank/layouts/blank/_find" > "$(date "+%Y%m%d_%H%M%S").txt"
Findメソッドの実行(レコード検索)

レスポンス(HEADER + JSON)

HTTP/2 200
server: nginx
date: Fri, 29 Mar 2024 17:20:10 GMT
content-type: application/json; charset=utf-8
content-length: 448
cache-control: no-cache
etag: W/"1c0-/SnZehBIpnq8INZJicGm/SnzJO4"
vary: Accept-Encoding
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
access-control-allow-origin: instance-5-pt9
access-control-allow-credentials: True
access-control-allow-headers: Content-Type,Authorization

{"response":{"dataInfo":{"database":"blank","layout":"blank","table":"blank","totalRecordCount":1,"foundCount":1,"returnedCount":1},"data":[{"fieldData":{"オブジェクトフィールド":"https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor","レコードID":1},"portalData":{},"recordId":"1","modId":"3"}]},"messages":[{"code":"0","message":"OK"}]}
Findレスポンス(Header + JSON)

レスポンス(JSON)

{
  "response": {
    "dataInfo": {
      "database": "blank",
      "layout": "blank",
      "table": "blank",
      "totalRecordCount": 1,
      "foundCount": 1,
      "returnedCount": 1
    },
    "data": [
      {
        "fieldData": {
          "オブジェクトフィールド": "https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor",
          "レコードID": 1
        },
        "portalData": {},
        "recordId": "1",
        "modId": "3"
      }
    ]
  },
  "messages": [
    {
      "code": "0",
      "message": "OK"
    }
  ]
}
Findレスポンス(JSONのみ抜粋)

オブジェクトフィールドのURLにGETリクエスト

上記のJSONのresponse.data[0].fieldData.オブジェクトフィールドにセットされているURLにGETリクエストします。

このURLをブラウザにコピペしてGETリクエストをすると、表示できます。

https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor
取得されたオブジェクトフィールドのURL
ブラウザでの画像表示
ブラウザでの画像表示

ブラウザのネットワークタブでリクエストを確認

ブラウザで画像が表示されるまでの裏側の挙動(HTTP通信)を整理すると、以下のフローになります。

概要

  • ブラウザは、URLに対して、GETリクエストする。
  • FileMaker Serverは、レスポンスヘッダーにCookie(X-FMS-Session-Key)を追加し、リダイレクト先のURL(location)を設定し、302ステータスでレスポンスを返す。
  • ブラウザは、ヘッダーにセットされたCookie(X-FMS-Session-Key)を保存し、リダイレクト先のURLに対して、GETリクエストする。
    • その際、ブラウザはCookieをヘッダーにセットして、リクエストを行います。
  • FileMaker Serverは、リクエストヘッダーのCookie(X-FMS-Session-Key)の値が一致していることを確認し、オブジェクトフィールドのバイナリを返す。
ネットワークタブ(最初)
URLに対して、GETリクエスト
ネットワークタブ(リダイレクト)
リダイレクト先のURLに対して、GETリクエスト

上記のブラウザと同じように、FileMaker Serverとcurlコマンドでやりとりすることで、オブジェクトフィールドをダウンロードできます。

curlコマンドによる実行手順の解説に戻ります

--verboseオプションについて

--verboseオプションを指定すると、詳細な情報を取得できます。

FileMakerでは--verboseオプションはサポートされていませんが、かわりに--traceオプションを使うことで、近しい情報を取得できます。

If your curl command does not execute or return what you expected it to, your first gut reaction should always be to run the command with the -v / --verbose option to get more information.

When verbose mode is enabled, curl gets more talkative and explains and shows a lot more of its doings.

It adds informational tests and prefix them with '*'. For example, let's see what curl might say when trying a simple HTTP example (saving the downloaded data in the file called 'saved'):

curl コマンドが実行されない、または期待どおりの結果が返されない場合、最初の直感的な反応は常に -v / --verbose オプションを指定してコマンドを実行して詳細情報を取得することです。

詳細モードを有効にすると、curl はより饒舌になり、より多くの動作を説明および表示します。

情報テストを追加し、先頭に「*」を付けます。

たとえば、単純な HTTP の例 (ダウンロードしたデータを「saved」というファイルに保存する) を試したときに、curl が何を言うかを見てみましょう。

Verbose - everything curl

curlのオプションについては、公式ドキュメントも併せて確認することをお勧めします。

curl - How To Use

リクエスト

--cookie-jarオプションを指定すると、サーバーのレスポンスヘッダーに含まれるSet-Cookieを取得できます。

curl --request "GET" \
  --verbose \
  --include \
  --cookie-jar ./cookiejar \
  "https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor" > "$(date "+%Y%m%d_%H%M%S").txt"
verboseオプション付きのリクエスト(Cookie確認用)

レスポンス(verbose)

Note: Unnecessary use of -X or --request, GET is already inferred.
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 35.213.59.44:443...
* Connected to fm.example.com (35.213.59.44) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [320 bytes data]
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [19 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [2588 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=fm.example.com
*  start date: Mar 29 14:03:47 2024 GMT
*  expire date: Jun 27 14:03:46 2024 GMT
*  subjectAltName: host "fm.example.com" matched cert's "fm.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: fm.example.com]
* [HTTP/2] [1] [:path: /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
> GET /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor HTTP/2
> Host: fm.example.com
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 302
< server: nginx
< date: Fri, 29 Mar 2024 17:24:23 GMT
< content-type: text/html
< content-length: 101
< location: /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect
* Added cookie X-FMS-Session-Key="958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0" for domain fm.example.com, path /Streaming_SSL/MainDB/, expire 0
< set-cookie: X-FMS-Session-Key=958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0; HttpOnly
< x-fms-result: 0
< strict-transport-security: max-age=31536000; includeSubDomains
< x-frame-options: SAMEORIGIN
< x-xss-protection: 1; mode=block
< x-content-type-options: nosniff
< access-control-allow-origin: instance-5-pt9
< access-control-allow-credentials: True
< access-control-allow-headers: Content-Type,Authorization
<
{ [101 bytes data]
100   101  100   101    0     0   2653      0 --:--:-- --:--:-- --:--:--  2729
* Connection #0 to host fm.example.com left intact
verboseログ(302リダイレクトとSet-Cookieの確認)

verboseを良く見ると、以下のようなログがあります。

* Added cookie X-FMS-Session-Key="958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0" for domain fm.example.com, path /Streaming_SSL/MainDB/, expire 0

ログの通りですが、FileMaker ServerがX-FMS-Session-KeyというCookieを追加しています。

そのため、cookiejarの中に、上記のX-FMS-Session-Keyが追加されます。

このX-FMS-Session-Keyをcookieに設定することで、オブジェクトフィールドがダウンロードできるようになります。

レスポンス(HEADER + HTML)

レスポンスのヘッダーを見ると302リダイレクトlocationset-cookieが設定さています。

また、set-cookieがあるので、cookieが--cookie-jarで指定した./cookiejarに保存されます。

HTTP/2 302
server: nginx
date: Fri, 29 Mar 2024 17:24:23 GMT
content-type: text/html
content-length: 101
location: /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect
set-cookie: X-FMS-Session-Key=958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0; HttpOnly
x-fms-result: 0
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
x-content-type-options: nosniff
access-control-allow-origin: instance-5-pt9
access-control-allow-credentials: True
access-control-allow-headers: Content-Type,Authorization

<html><head><title>Moved Temporarily</title></head><body><h1>302 Moved Temporarily</h1></body></html>
302 Moved Temporarily のレスポンス(Header + HTML)

レスポンス(HTML)

<html>
<head><title>Moved Temporarily</title></head>
<body><h1>302 Moved Temporarily</h1></body>
</html>
302 Moved Temporarily のレスポンスボディ(HTML)

Cookie(./cookiejar)

# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_fm.example.com	FALSE	/Streaming_SSL/MainDB/	FALSE	0	X-FMS-Session-Key	958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0
保存されたCookieファイル(cookiejar)の内容

この次に、locationのURLにGETリクエストします。その際に、./cookiejar--cookieオプションで指定します。

locationのURLにGETリクエスト

リクエスト

curl --request "GET" \
  --verbose \
  --cookie ./cookiejar \
  --output "download.png" \
  "https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect"
Cookieを指定してリダイレクト先へアクセス

レスポンス(verbose)

Note: Unnecessary use of -X or --request, GET is already inferred.
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 35.213.59.44:443...
* Connected to fm.example.com (35.213.59.44) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [320 bytes data]
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [19 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [2588 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=fm.example.com
*  start date: Mar 29 14:03:47 2024 GMT
*  expire date: Jun 27 14:03:46 2024 GMT
*  subjectAltName: host "fm.example.com" matched cert's "fm.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: fm.example.com]
* [HTTP/2] [1] [:path: /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [cookie: X-FMS-Session-Key=958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0]
> GET /Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor&Redirect HTTP/2
> Host: fm.example.com
> User-Agent: curl/8.4.0
> Accept: */*
> Cookie: X-FMS-Session-Key=958B4BE4707B39041058C16C1C128DA80184AB8D21F61A4492CF693FBA04ACE0
>
< HTTP/2 200
< server: nginx
< date: Fri, 29 Mar 2024 17:57:05 GMT
< content-type: image/png
< content-length: 415518
< cache-control: max-age=8640001
< accept-ranges: bytes
< x-fms-result: 0
< strict-transport-security: max-age=31536000; includeSubDomains
< x-frame-options: SAMEORIGIN
< x-xss-protection: 1; mode=block
< x-content-type-options: nosniff
< access-control-allow-origin: instance-5-pt9
< access-control-allow-credentials: True
< access-control-allow-headers: Content-Type,Authorization
<
{ [8027 bytes data]
100  405k  100  405k    0     0   520k      0 --:--:-- --:--:-- --:--:--  520k
* Connection #0 to host fm.example.com left intact
画像データ取得時のverboseログ

レスポンス(download.png)

無事、オブジェクトフィールドに保存してあったpngファイルがダウンロードできました。

ダウンロードしたPNG画像
download.png

もう少し簡単に書く

上記の説明は、なにが起こっているか理解するために、わざと--locationオプションを指定せずに、実行しました。

本来は、もう少し簡単に書くことができます。

オブジェクトフィールドのURLにGETリクエスト(--locationを追加)

以下のコマンドで、オブジェクトフィールドのデータを取得できます。

curl --request "GET" \
  --verbose \
  --location \
  --cookie-jar ./cookiejar \
  --cookie ./cookiejar \
  --output download.png \
  "https://fm.example.com:443/Streaming_SSL/MainDB/2FA9BAA884B524D0DE34C298FE3EDEC3E2171AF1EC93D510CBCA3772E63E0876.png?RCType=EmbeddedRCFileProcessor"
リダイレクトとCookie処理をまとめたcurlコマンド

FileMakerのcurlオプションの書き方

FileMakerのURLから挿入を使う場合の、curlオプションの記述例を示します。

--request "GET"
--location
--cookie-jar $cookiejar
--cookie $cookiejar
--output download.png
--FM-return-container-variable
FileMaker「URLから挿入」のcURLオプション計算式
FM計算式ダイアログ
curlオプションの計算式

--FM-return-container-variableオプションは、戻り値のバイナリを変数に設定できる便利なオプションです。

このオプションを活用することで、レイアウト上に該当のオブジェクトフィールドが配置されていなくても、データを変数として直接取得できるため非常に便利です。

[URL から挿入] が変数をターゲットとする場合、このオプションを使用して、返されるデータをオブジェクトデータとして強制的に保存します。

サポートされている cURL オプション | Claris FileMaker Pro 2023 ヘルプ
ヘルプ画面
--FM-return-container-variableのヘルプ

スクリプト(テキスト)

# curl in file blank (fm.example.com)

#
# --- オプション
// # --request "GET"
--location
--cookie-jar $cookiejar
--cookie $cookiejar
--output download.png
--FM-return-container-variable
テキストを挿入 [ 選択 ; ターゲット: $オプション ; 「--request "GET" --location --cookie-jar $cookiejar --cookie $cookiejar --output download.png --FM-return-container-variable」 ]
#
# --- url
変数を設定 [ $url ; 値: "https://fm.example.com:443/Streaming_SSL/MainDB/D2B80086AB0CEEC39DE181602AAA3D5454300BBF6320963257B2105053010C40.png?RCType=EmbeddedRCFileProcessor" ]
#
# --- curl
URL から挿入 [ 選択 ; ダイアログあり: オフ ; ターゲット: $バイナリ ; $url ; cURL オプション: $オプション ]
#
変数を設定 [ $debug ; 値: GetContainerAttribute ( $バイナリ ; "all" ) ]
#
現在のスクリプト終了 [ テキスト結果:    ]
#
検証用スクリプト(テキスト)

スクリプト(スクリーンショット)

スクリプト
スクリプトのスクリーンショット

$debugの変数をデータビューアで確認すると、バイナリとして設定されていることが分かります。

変数を設定 [ $debug ; 値: GetContainerAttribute ( $バイナリ ; "all" ) ]
データビューアでの戻り値確認(GetContainerAttribute)
データビューア
データビューアでの確認結果($debug 変数)
小巻旭洋のプロフィール写真

小巻 旭洋

2014年からフリーランスとして活動し、2016年に株式会社フルーデンスを設立する。FileMaker開発歴は約10年。多数の企業システム構築を手がけ、特にデータベース設計と、JavaScript連携、Web連携、パフォーマンス最適化を専門としています。2025年から、Web業務システム・アプリ開発をメインに開発をしています。