0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

htmx 入門:curl との比較で HTTP ヘッダーと CORS を確認する

0
Last updated at Posted at 2026-06-10

:この記事では htmx.org@2.0.10 を使います。

htmx v4 は現在 beta 版として開発中で、4.0.0-beta4 も公開されています。
ただし、この記事は初心者向けに HTTP ヘッダー、CORS、HX-* ヘッダーの観察を目的とするため、安定版の htmx v2 系で動作確認します。

v4 系を試す場合は、CDN や npm のバージョン指定を変更したうえで、selfRequestsOnly、CORS、swap 挙動、イベントまわりの違いを確認してください。

はじめに

htmx は、HTML の属性だけで HTTP リクエストを送れる便利なライブラリです。

たとえば、次のように書くと、ボタンを押したときに GET リクエストを送れます。

<button hx-get="/hello" hx-target="#result">
  取得する
</button>

<div id="result"></div>

この手軽さだけを見ると、htmx は「画面を簡単に更新するライブラリ」に見えます。

しかし、実際に使ってみると、htmx は ブラウザーから HTTP リクエストを送る練習道具としても便利です。

一方で、curl とは違う注意点もあります。

curl はターミナルから直接 HTTP リクエストを送ります。
htmx はブラウザーから HTTP リクエストを送ります。

そのため、htmx では次のような確認が必要になります。

  • CORS
  • Origin
  • Sec-Fetch-* ヘッダー
  • htmx が付ける HX-* ヘッダー
  • Cookie の自動送信
  • ブラウザーのセキュリティ制約

この記事では、curl と htmx を比較しながら、HTTP ヘッダーと CORS の基本を確認します。

この記事で使う練習サイト

この記事では、次の URL を使います。

https://nghttp2.org/httpbin

nghttp2.org は、HTTP/2 ライブラリである nghttp2 のプロジェクトサイトです。
また、HTTP/3 関連では nghttp3 というライブラリもあり、curl の HTTP/2 / HTTP/3 対応を学ぶときにも名前が出てきます。

つまり、nghttp2.org/httpbin は、単なる httpbin 互換サービスとしてだけでなく、HTTP/2 や HTTP/3 の学習文脈にもつなげやすい練習先です。

今回はまず、HTTP/2 / HTTP/3 の深い話には入りません。
初心者向けに、次のことを確認します。

  • curl で GET リクエストする
  • htmx で同じ GET リクエストを送る
  • 返ってきた HTTP ヘッダーを見る
  • CORS に関係するヘッダーを見る
  • htmx が自動で送るヘッダーを見る

今回のゴール

この記事のゴールは、htmx で複雑な UI を作ることではありません。

ゴールは次の3つです。

1. curl で HTTP リクエストの基本形を見る
2. htmx でブラウザーから HTTP リクエストを送る
3. curl では意識しにくいブラウザー由来のヘッダーを確認する

特に重要なのは、次の教訓です。

curl で動く HTTP リクエストが、ブラウザーからもそのまま動くとは限らない。

htmx を使うと、この違いを実際に観察できます。

curl と htmx の違い

まず、curl と htmx の違いを整理します。

観点 curl htmx
実行場所 ターミナル ブラウザー
CORS 基本的に関係ない 影響を受ける
Cookie 明示しない限り送らない ブラウザーが自動送信する場合がある
Origin 通常は付かない 外部 API では付く
Sec-Fetch-* 付かない ブラウザーが付ける
HX-* 付かない htmx が付ける
用途 HTTP の最小形を確認 ブラウザー文脈込みの HTTP を確認

curl は、HTTP リクエストの最小形を見る道具です。

htmx は、ブラウザーから送られる HTTP リクエストを見る道具です。

どちらも HTTP の学習に役立ちますが、見えるものが違います。

1. curl で GET リクエストする

まずは curl で GET リクエストを送ります。

curl -s 'https://nghttp2.org/httpbin/get?keyword=htmx&page=1'

JSON を見やすくするために、jq が使える場合は次のようにします。

curl -s 'https://nghttp2.org/httpbin/get?keyword=htmx&page=1' | jq .

たとえば、次のようなレスポンスが返ります。

{
  "args": {
    "keyword": "htmx",
    "page": "1"
  },
  "headers": {
    "Accept": "*/*",
    "Host": "nghttp2.org",
    "User-Agent": "curl/..."
  },
  "origin": "<your-ip-address>",
  "url": "https://nghttp2.org/httpbin/get?keyword=htmx&page=1"
}

ここで確認したいのは、args です。

"args": {
  "keyword": "htmx",
  "page": "1"
}

URL のクエリ文字列が、サーバー側ではこのように解釈されています。

?keyword=htmx&page=1

つまり、curl で次のことを確認できます。

  • URL が正しいか
  • クエリパラメーターが届いているか
  • API が生きているか
  • JSON レスポンスが返るか

2. curl でレスポンスヘッダーを見る

次に、レスポンスヘッダーを確認します。

curl -I 'https://nghttp2.org/httpbin/get'

私の環境では、次のようなヘッダーが返りました。

HTTP/2 200
content-type: application/json
access-control-allow-origin: *
access-control-allow-credentials: true
strict-transport-security: max-age=31536000
server: nghttpx
alt-svc: h3=":443"; ma=3600

ここで注目したいのは、次のヘッダーです。

access-control-allow-origin: *

これは CORS に関係するヘッダーです。

ざっくり言うと、外部のブラウザーからこのリソースを読めるかどうかに関係します。

* は、認証情報を含まないリクエストであれば、広い origin からのアクセスを許可するという意味です。

ただし、これだけで何でも安全に呼べるわけではありません。

Cookie や Authorization ヘッダーなど、認証情報を含むリクエストでは話が変わります。

3. htmx で GET リクエストする

次に、htmx で同じ URL にリクエストしてみます。

test.html を作ります。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>htmx HTTP header demo</title>
  <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js"></script>

  <script>
    htmx.config.selfRequestsOnly = false;
  </script>
</head>
<body>
  <h1>htmx HTTP header demo</h1>

  <button
    hx-get="https://nghttp2.org/httpbin/get?keyword=htmx&page=1"
    hx-target="#result"
    hx-swap="innerHTML">
    GETする
  </button>

  <h2>Result</h2>
  <pre id="result">ここに結果が表示されます。</pre>
</body>
</html>

ローカルサーバーで開きます。Node.js、Python、PHP などの選択肢があります。

npx serve -l 8888
python3 -m http.server 8888
php -S localhost:8888

ブラウザーで開きます。

http://localhost:8888/test.html

ボタンを押すと、https://nghttp2.org/httpbin/get?keyword=htmx&page=1 にリクエストが送られ、レスポンス JSON が <pre id="result"> に表示されます。

4. selfRequestsOnly = false とは何か

先ほどの HTML には、次の設定を入れました。

<script>
  htmx.config.selfRequestsOnly = false;
</script>

htmx v2 では、デフォルトでは同一オリジンへのリクエストだけを許可します。

今回のページは次の URL で開いています。

http://localhost:8888/test.html

リクエスト先は次です。

https://nghttp2.org/httpbin/get?keyword=htmx&page=1

これは外部サイトです。

そのため、htmx 側の制限を外すために selfRequestsOnly = false を設定しています。

ただし、注意点があります。

selfRequestsOnly = false は、htmx 側の制限を外すだけです。
ブラウザーの CORS 制約を解除するものではありません。

つまり、外部 API 側が CORS を許可していなければ、やはり失敗します。

5. htmx から送られたリクエストを確認する

htmx で GET した結果は、たとえば次のようになります。

{
  "args": {
    "keyword": "htmx",
    "page": "1"
  },
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate, br, zstd",
    "Accept-Language": "ja,en;q=0.9,en-US;q=0.8,fr;q=0.7",
    "Host": "nghttp2.org",
    "Hx-Current-Url": "http://localhost:8888/test.html",
    "Hx-Request": "true",
    "Hx-Target": "result",
    "Origin": "http://localhost:8888",
    "Referer": "http://localhost:8888/",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "cross-site",
    "User-Agent": "Mozilla/5.0 ..."
  },
  "origin": "<your-ip-address>",
  "url": "https://nghttp2.org/httpbin/get?keyword=htmx&page=1"
}

curl のときより、たくさんのヘッダーが増えています。

これは、htmx が悪いわけではありません。
ブラウザーから送っているためです。

6. htmx が付ける HX-* ヘッダー

まず、htmx が付けるヘッダーを見ます。

"Hx-Current-Url": "http://localhost:8888/test.html",
"Hx-Request": "true",
"Hx-Target": "result"

それぞれの意味は次の通りです。

ヘッダー 意味
Hx-Request: true htmx からのリクエストである
Hx-Target: result 差し替え対象の要素が #result
Hx-Current-Url htmx が認識している現在のページ URL

サーバー側では、Hx-Request を見て、htmx からのリクエストかどうかを判定できます。

たとえば、Node.js なら次のような分岐ができます。

if (req.headers["hx-request"] === "true") {
  // htmx 向けに HTML 断片を返す
} else {
  // 通常アクセス向けにページ全体を返す
}

これが htmx の重要なポイントです。

htmx は単に HTTP リクエストを送るだけではありません。
サーバー側から見ると、「これは htmx から来たリクエストです」と判定できる情報も送っています。

7. ブラウザーが付ける Origin

次に、Origin を見ます。

"Origin": "http://localhost:8888"

これは、ブラウザーが付けたヘッダーです。

意味は次の通りです。

このリクエストは http://localhost:8888 のページから送られました。

CORS では、この Origin が重要です。

ブラウザーは外部 API にリクエストするとき、Origin を送ります。
外部 API は、それに対して「許可します」というレスポンスヘッダーを返す必要があります。

その代表がこれです。

Access-Control-Allow-Origin: *

または、特定の origin だけを許可する場合は次のようになります。

Access-Control-Allow-Origin: http://localhost:8888

初心者向けには、こう覚えるとよいです。

Origin:
  ブラウザーが「このページから来ました」と送る

Access-Control-Allow-Origin:
  サーバーが「そのページから来てもよいです」と返す

8. Sec-Fetch-* ヘッダーを見る

次に、Sec-Fetch-* ヘッダーを見ます。

"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site"

これらは、ブラウザーが自動で付けるヘッダーです。

Sec-Fetch-Site

"Sec-Fetch-Site": "cross-site"

これは、リクエスト元とリクエスト先の関係を示します。

今回のページは次です。

http://localhost:8888/test.html

リクエスト先は次です。

https://nghttp2.org/httpbin/get?keyword=htmx&page=1

別サイトなので、cross-site になっています。

もし、同じ origin の API にリクエストする場合は、same-origin になります。

Sec-Fetch-Mode

"Sec-Fetch-Mode": "cors"

これは、CORS モードのリクエストであることを示します。

今回のように、ブラウザーから外部 API にリクエストする場合に関係します。

Sec-Fetch-Dest

"Sec-Fetch-Dest": "empty"

これは、リクエストの用途を示します。

empty は、画像や CSS や script の読み込みではなく、fetch / XMLHttpRequest のようなプログラム的なリクエストであることを意味します。

htmx の hx-get は HTML 属性で書きますが、内部的にはブラウザーからプログラム的な HTTP リクエストを送っています。

そのため、Sec-Fetch-Dest: empty になります。

9. curl と htmx のヘッダーを比較する

curl のリクエストはシンプルです。

curl -s 'https://nghttp2.org/httpbin/get?keyword=htmx&page=1' | jq '.headers'

一方、htmx から送ると、次のようなヘッダーが追加されます。

Hx-Request
Hx-Target
Hx-Current-Url
Origin
Referer
Sec-Fetch-Site
Sec-Fetch-Mode
Sec-Fetch-Dest
Sec-Ch-Ua
Sec-Ch-Ua-Platform
Cookie があれば Cookie

この違いが重要です。

curl は HTTP の最小形を見るのに向いています。
htmx はブラウザーが実際に送る HTTP リクエストを見るのに向いています。

同じ URL に GET していても、送られるヘッダーは同じではありません。

10. よくあるエラー

ここからは、初心者がつまずきやすいエラーを整理します。

htmx:invalidPath

外部 URL にリクエストしようとして、htmx 側で拒否されることがあります。

原因は、htmx v2 の selfRequestsOnly です。

対応は次です。

<script>
  htmx.config.selfRequestsOnly = false;
</script>

ただし、これは htmx 側の制限を外すだけです。
CORS エラーを解決するものではありません。

CORS エラー

ブラウザーの Console に次のようなエラーが出ることがあります。

Access to XMLHttpRequest at 'https://example.com/...'
from origin 'http://localhost:8888'
has been blocked by CORS policy

これは、外部 API 側がブラウザーからのアクセスを許可していないときに起きます。

curl では成功しても、htmx では失敗することがあります。

理由は、curl は CORS の対象ではないからです。

Request header field hx-target is not allowed

次のようなエラーが出ることがあります。

Request header field hx-target is not allowed by Access-Control-Allow-Headers

これは、htmx が送る HX-Target などのヘッダーを、外部 API 側が CORS で許可していない場合に起きます。

外部 API 側で次のようなヘッダーが必要になります。

Access-Control-Allow-Headers: HX-Request, HX-Target, HX-Current-URL

ただし、外部 API の設定は自分では変更できません。

その場合は、直接ブラウザーから叩くのではなく、自分のサーバーを中継させます。

11. 外部 API を htmx で試す前の確認手順

今回の作業でわかった教訓は、確認順序が大事だということです。

おすすめのワークフローは次です。

1. curl で API が生きているか確認する
2. curl -I でレスポンスヘッダーを見る
3. Access-Control-Allow-Origin を確認する
4. htmx v2 では selfRequestsOnly = false を設定する
5. まず GET だけで試す
6. DevTools の Network タブを見る
7. Origin と Access-Control-Allow-Origin を確認する
8. HX-* ヘッダーが CORS で許可されるか確認する
9. API トークンが必要なものはブラウザーから直接呼ばない

特に、最初は GET だけで確認するのがおすすめです。

POST、JSON、独自ヘッダー、Authorization などを入れると、CORS preflight が絡んで難しくなります。

12. API トークンを HTML に書いてはいけない

htmx を使うと、HTML だけで外部 API を呼べるように見えます。

しかし、次のように API トークンを HTML に書いてはいけません。

<!-- 実運用ではやってはいけない例 -->
<button
  hx-get="https://api.example.com/data"
  hx-headers='{"Authorization":"Bearer sk-xxxxx"}'>
  呼び出す
</button>

HTML はブラウザーに配布されます。
つまり、利用者から見えます。

AI API や業務 API のトークンを使う場合は、ブラウザーから直接外部 API を呼ぶのではなく、自分のサーバーを中継させます。

ブラウザー / htmx
  ↓
自社 API サーバー
  ↓
外部 API

API トークンは、自社 API サーバー側の環境変数などに置きます。

13. htmx は curl の代わりになるのか

今回の実験を通して、htmx は curl のような練習道具として便利だと感じました。

ただし、完全な代わりではありません。

curl は、HTTP の最小形を確認するのに向いています。

curl -i 'https://nghttp2.org/httpbin/get?keyword=htmx&page=1'

htmx は、ブラウザーから送られる HTTP を確認するのに向いています。

<button
  hx-get="https://nghttp2.org/httpbin/get?keyword=htmx&page=1"
  hx-target="#result">
  GETする
</button>

curl では、CORS や Sec-Fetch-* を意識する必要はあまりありません。
htmx では、ブラウザーから送るため、それらを確認する必要があります。

つまり、次のように使い分けるとよいです。

用途 道具
API が生きているか確認する curl
HTTP ヘッダーを最小構成で見る curl
ブラウザーから送ると何が増えるか見る htmx
CORS を確認する htmx + DevTools
サーバー側で htmx 判定をしたい HX-* ヘッダー

まとめ

この記事では、curl と htmx を比較しながら、HTTP ヘッダーと CORS を確認しました。

学んだことは次の通りです。

  • curl は HTTP リクエストの最小形を見るのに便利
  • htmx はブラウザーから HTTP リクエストを送る練習に便利
  • htmx v2 で外部 URL を呼ぶには selfRequestsOnly = false が必要
  • 外部 API では CORS の確認が必要
  • htmx は HX-RequestHX-TargetHX-Current-URL などを送る
  • ブラウザーは OriginSec-Fetch-* ヘッダーを送る
  • curl で成功しても、ブラウザーから成功するとは限らない
  • API トークンを HTML に書いてはいけない

今回の教訓は、次の一文にまとめられます。

htmx は curl のように HTTP リクエストを試せる便利な道具だが、ブラウザーのセキュリティ境界の中で動くため、curl では求められない確認作業が必要になる。

htmx を使うと、HTTP リクエストが HTML から見えるようになります。
同時に、ブラウザーがどのようなヘッダーを付け、どのような制約をかけているのかも見えてきます。

初心者が HTTP を学ぶ入口として、curl と htmx を並べて使うのはかなり有効だと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?