アクションを連結する
あるアクションの出力を次のアクションへそのまま渡す方法を、3段階のパイプラインの実例と、連結が適さないケースとともに説明します。
すべてのアクションは PDF を入力として受け取り、PDF を出力として返します。そのため、あるアクションの出力は次のアクションの正当な入力になります。これにより、単機能のアクションを組み合わせて複数段階のパイプラインを構築できます。たとえば、複数のファイルを結合し、その結果に透かしを付与してから暗号化するという処理を、1つのスクリプトの中で、バイト列を呼び出しごとにメモリ上で受け渡ししながら行えます。
この API はステートレス(リクエストの間に何も保存されない)であるため、再利用するハンドルもクリーンアップの必要もありません。各段階は、レスポンスの本文をそのまま次の段階へ file フィールドとして渡すだけです。
仕組み
単一出力のアクションは、200 OK と Content-Type: application/pdf を返します。本文は完全な PDF です。アクションを連結するには、ディスクからファイルを読み込む代わりに、この本文を次のリクエストの file パートとして送信します。ファイルシステムに触れる必要があるのは、最初の入力と最後の出力だけです。その間はすべてメモリ上にとどまります。
3段階のパイプライン
この例では、2つの元ファイルを1つの暗号化・透かし付きドキュメントへと変換します。
POST /v1/merge_documents を cover.pdf と report.pdf に対して実行すると、結合された1つの PDF が返されます。入力オプションについてはPDF ドキュメントを結合するを参照してください。
その PDF を file フィールドとして POST /v1/add_text_watermark に送信します。レスポンスは、すべてのページに透かしが付与された同じドキュメントです。
透かしが付与された PDF を、password とともに POST /v1/add_password に送信します。レスポンスは最終的な保護済みドキュメントです。これをディスクに書き込みます。
結合アクションは、入力 PDF を繰り返しの file パートとして受け取れるほか、フィールド名を繰り返すのが扱いにくい言語(PHP、Ruby)向けに、file_1 から file_10 までの番号付きフィールドとしても受け取れます。以下では両方の形式を示しています。11個以上のファイルがある場合は、繰り返しの file を使用してください。
# Each stage reads the previous PDF from stdin via `-F 'file=@-'`.
curl -sS https://api.pdfblocks.com/v1/merge_documents \
-H 'X-API-Key: your_api_key' \
-F file=@cover.pdf \
-F file=@report.pdf |
curl -sS https://api.pdfblocks.com/v1/add_text_watermark \
-H 'X-API-Key: your_api_key' \
-F 'file=@-;filename=merged.pdf;type=application/pdf' \
-F line_1='CONFIDENTIAL' \
-F line_2='ACME, Inc.' |
curl -sS https://api.pdfblocks.com/v1/add_password \
-H 'X-API-Key: your_api_key' \
-F 'file=@-;filename=watermarked.pdf;type=application/pdf' \
-F password='pa$$word' \
-o final.pdf# pip install requests
import requests
BASE = 'https://api.pdfblocks.com'
HEADERS = {'X-API-Key': 'your_api_key'}
# 1. Merge cover.pdf and report.pdf into one document.
with open('cover.pdf', 'rb') as cover, open('report.pdf', 'rb') as report:
merged = requests.post(
f'{BASE}/v1/merge_documents',
headers=HEADERS,
files=[('file', cover), ('file', report)],
)
merged.raise_for_status()
# 2. Watermark the merged bytes: no temp file.
watermarked = requests.post(
f'{BASE}/v1/add_text_watermark',
headers=HEADERS,
files={'file': ('merged.pdf', merged.content, 'application/pdf')},
data={'line_1': 'CONFIDENTIAL', 'line_2': 'ACME, Inc.'},
)
watermarked.raise_for_status()
# 3. Encrypt the watermarked bytes.
final = requests.post(
f'{BASE}/v1/add_password',
headers=HEADERS,
files={'file': ('watermarked.pdf', watermarked.content, 'application/pdf')},
data={'password': 'pa$$word'},
)
final.raise_for_status()
with open('final.pdf', 'wb') as output:
output.write(final.content)// Node.js 18+
import { readFile, writeFile } from 'node:fs/promises';
const BASE = 'https://api.pdfblocks.com';
const headers = { 'X-API-Key': 'your_api_key' };
// Post a multipart form and return the response PDF as bytes.
async function post(path, form) {
const response = await fetch(BASE + path, { method: 'POST', headers, body: form });
if (!response.ok) throw new Error(`${path} failed: ${response.status}`);
return new Uint8Array(await response.arrayBuffer());
}
// 1. Merge cover.pdf and report.pdf.
const mergeForm = new FormData();
mergeForm.append('file', new Blob([await readFile('cover.pdf')]), 'cover.pdf');
mergeForm.append('file', new Blob([await readFile('report.pdf')]), 'report.pdf');
const merged = await post('/v1/merge_documents', mergeForm);
// 2. Watermark the merged bytes.
const watermarkForm = new FormData();
watermarkForm.append('file', new Blob([merged]), 'merged.pdf');
watermarkForm.append('line_1', 'CONFIDENTIAL');
watermarkForm.append('line_2', 'ACME, Inc.');
const watermarked = await post('/v1/add_text_watermark', watermarkForm);
// 3. Encrypt the watermarked bytes.
const passwordForm = new FormData();
passwordForm.append('file', new Blob([watermarked]), 'watermarked.pdf');
passwordForm.append('password', 'pa$$word');
const final = await post('/v1/add_password', passwordForm);
await writeFile('final.pdf', final);<?php
$base = 'https://api.pdfblocks.com';
$headers = ['X-API-Key: your_api_key'];
// Post a multipart form and return the response body, or throw on error.
function post(string $url, array $headers, array $fields): string {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $fields,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 200) {
throw new RuntimeException("Request to $url failed with status $status.");
}
return $body;
}
// 1. Merge cover.pdf and report.pdf (numbered fields for the array input).
$merged = post("$base/v1/merge_documents", $headers, [
'file_1' => new CURLFile('cover.pdf', 'application/pdf'),
'file_2' => new CURLFile('report.pdf', 'application/pdf'),
]);
// 2. Watermark the merged bytes in memory (CURLStringFile, PHP 8.1+).
$watermarked = post("$base/v1/add_text_watermark", $headers, [
'file' => new CURLStringFile($merged, 'merged.pdf', 'application/pdf'),
'line_1' => 'CONFIDENTIAL',
'line_2' => 'ACME, Inc.',
]);
// 3. Encrypt the watermarked bytes.
$final = post("$base/v1/add_password", $headers, [
'file' => new CURLStringFile($watermarked, 'watermarked.pdf', 'application/pdf'),
'password' => 'pa$$word',
]);
file_put_contents('final.pdf', $final);# gem install http
require 'http'
require 'stringio'
BASE = 'https://api.pdfblocks.com'
HEADERS = { 'X-API-Key' => 'your_api_key' }
def post(path, fields)
response = HTTP.headers(HEADERS).post("#{BASE}#{path}", form: fields)
raise "#{path} failed: #{response.status}" unless response.status.success?
response.body.to_s
end
# 1. Merge cover.pdf and report.pdf (numbered fields for the array input).
merged = post('/v1/merge_documents',
file_1: HTTP::FormData::File.new('cover.pdf'),
file_2: HTTP::FormData::File.new('report.pdf'))
# 2. Watermark the merged bytes in memory.
watermarked = post('/v1/add_text_watermark',
file: HTTP::FormData::File.new(StringIO.new(merged),
filename: 'merged.pdf', content_type: 'application/pdf'),
line_1: 'CONFIDENTIAL',
line_2: 'ACME, Inc.')
# 3. Encrypt the watermarked bytes.
final = post('/v1/add_password',
file: HTTP::FormData::File.new(StringIO.new(watermarked),
filename: 'watermarked.pdf', content_type: 'application/pdf'),
password: 'pa$$word')
File.write('final.pdf', final)package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
const base = "https://api.pdfblocks.com"
// post sends a multipart form and returns the response PDF bytes.
func post(path string, build func(*multipart.Writer)) []byte {
var buf bytes.Buffer
form := multipart.NewWriter(&buf)
build(form)
form.Close()
req, _ := http.NewRequest("POST", base+path, &buf)
req.Header.Set("Content-Type", form.FormDataContentType())
req.Header.Set("X-API-Key", "your_api_key")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("%s failed: %d", path, res.StatusCode))
}
return body
}
func addFile(form *multipart.Writer, name string, data []byte) {
part, _ := form.CreateFormFile("file", name)
part.Write(data)
}
func main() {
cover, _ := os.ReadFile("cover.pdf")
report, _ := os.ReadFile("report.pdf")
// 1. Merge cover.pdf and report.pdf.
merged := post("/v1/merge_documents", func(form *multipart.Writer) {
addFile(form, "cover.pdf", cover)
addFile(form, "report.pdf", report)
})
// 2. Watermark the merged bytes.
watermarked := post("/v1/add_text_watermark", func(form *multipart.Writer) {
addFile(form, "merged.pdf", merged)
form.WriteField("line_1", "CONFIDENTIAL")
form.WriteField("line_2", "ACME, Inc.")
})
// 3. Encrypt the watermarked bytes.
final := post("/v1/add_password", func(form *multipart.Writer) {
addFile(form, "watermarked.pdf", watermarked)
form.WriteField("password", "pa$$word")
})
os.WriteFile("final.pdf", final, 0644)
}using System.Net.Http.Headers;
const string Base = "https://api.pdfblocks.com";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");
// Post a multipart form and return the response PDF bytes.
async Task<byte[]> Post(string path, Action<MultipartFormDataContent> build)
{
using var form = new MultipartFormDataContent();
build(form);
var response = await client.PostAsync(Base + path, form);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsByteArrayAsync();
}
static void AddFile(MultipartFormDataContent form, string name, byte[] data)
{
var content = new ByteArrayContent(data);
content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(content, "file", name);
}
// 1. Merge cover.pdf and report.pdf.
var merged = await Post("/v1/merge_documents", form =>
{
AddFile(form, "cover.pdf", File.ReadAllBytes("cover.pdf"));
AddFile(form, "report.pdf", File.ReadAllBytes("report.pdf"));
});
// 2. Watermark the merged bytes.
var watermarked = await Post("/v1/add_text_watermark", form =>
{
AddFile(form, "merged.pdf", merged);
form.Add(new StringContent("CONFIDENTIAL"), "line_1");
form.Add(new StringContent("ACME, Inc."), "line_2");
});
// 3. Encrypt the watermarked bytes.
var final = await Post("/v1/add_password", form =>
{
AddFile(form, "watermarked.pdf", watermarked);
form.Add(new StringContent("pa$$word"), "password");
});
await File.WriteAllBytesAsync("final.pdf", final);各段階を次に渡す前に確認する
失敗した段階は、PDF ではなく 4XX と application/problem+json 本文を返します。このエラー本文を次のリクエストへ file として渡してしまうと、次の段階も失敗し、分かりにくいメッセージになります。本文を渡す前に、すべてのレスポンスのステータスを確認してください。上記の例はいずれも 200 以外のステータスで例外を送出します。レスポンスの形式についてはエラーを参照してください。
暗号化する前に、透かしの付与と結合を済ませてください。PDF にパスワードを追加するがドキュメントを一度暗号化すると、それ以降のアクションはそのドキュメントを開けなくなります。そのため、パスワードを扱う段階は必ずパイプラインの最後に置いてください。順序に関する完全なルールについては、ドキュメントを保護してロックを解除するを参照してください。
連結すべきとき、すべきでないとき
各段階がそれぞれ独立した変換であり、順番に適用したい場合は連結を使います。各段階は純粋で単機能な呼び出しのままにしてください。そうすることで、あるアクションの出力が次のアクションの有効な入力になり、失敗が起きた箇所も特定しやすくなります。
連結のように見えて実際にはそうではない処理もあります。ページの並べ替えと除外を1回の処理で行うのは、抽出に続けて結合するのではなく、PDF のページを並べ替えるの単独の呼び出しで済みます。パイプラインを使うのは、単一のアクションでは処理全体をこなせない場合に限ってください。