- 投稿日:2019-03-07T00:12:14+09:00
サブディレクトリ URL で起動する Angular の Web アプリで API 通信を実装したらハマった話+解消手順
概要
Angular (+ express) + Node.js でバックエンドに API 通信をさせた時にエラーでハマった経緯と、その解消策の備忘録
先に結論から言うと
「Nginx の設定をミスったら、バックエンド通信に失敗するよ」と言う話。
経緯
Angular + express で MEAN スタックで作成した Web アプリ。
今までは普通に、開発 ➡️ サーバへデプロイ ➡️ 実機で動作確認、で問題なく動いていた。
ただし、ドメイン取得して、サーバのトップレベルで動作する Web サイトとして作っていた。今回は、サブディレクトリでのみ動作する Web サイトとして開発。
つまり...
https://example.com▶︎ 別の Web サイト(静的 HTML ページ)https://example.com/my-app▶︎ 今回の Angular サイトとしようとした。
実際に、Angular でアプリを開発して配置。事象と解消手順
事象1: 画面表示でエラー発生
https://example.com/my-appにアクセスしたら、テキストだけ出るけど装飾系が描画されない。と言う中途半端な状態で画面表示。表示が壊れている状態。。。
もちろん、https://example.comは問題なく規定の Web ページが表示される。▶︎ 解消: 「base href」を設定したら解消
index.html の
<base href="/">が<base href="/my-app/">となると解消する。
しかし、ローカル環境では必要ない...。
なので、以下のとおり、package.json のビルドコマンドに--base-hrefオプションを指定することで、ビルド時に「base href」を変え分けられるようにした。package.json: "scripts": { // 起動コマンド "dev": "node ./dist/server/server.js", // ビルドコマンド (ng build に --base-href オプション追加) "build": "npm run build:client && npm run build:server", "build:client": "ng build --prod --base-href=/my-app/ && ng run my-app:server", "build:server": "tsc --project server --outDir dist --allowjs true", : }, :
事象2: バックエンドへの通信時にエラー発生
「事象1」が解消し、画面表示はできるようになったが、
今度は、API でバックエンドにリスト取得する処理を実装したら以下のエラー事象が発生。ローカルだと取得できてたのに、サーバだとリスト取得できない。。。(本来なら JSON のリストデータが返却される予定)
ブラウザコンソールを開いてみたら、以下のエラーログを吐いていた。# 発生したエラー error: SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse... headers: t {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ} message: "Http failure during parsing for https://iexample.com/portal/api/my-app/api/heros" name: "HttpErrorResponse" ok: false status: 200 statusText: "OK" url: "https://example.com/my-app/api/heros"バックエンド通信で、HTTP 結果コードは 200 が返っているが、返却された後に何かエラーが出ている。
errorの部分を開いて詳しく見てみるとSyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequestさらに、すぐ下に
text: "<!DOCTYPE html><html lang="ja"><head>↵ <meta charset="utf-8">↵ <title>My App</title>↵JSON データ、もしくは、 JSON のリストデータが返却されるべき箇所で、HTML を返しているために、JSON.parse() でエラーが発生したことが原因らしい。
長く悩んだが、よく考えたらそりゃそうだ。Web サービスで画面用のルーティングしか設定してなかった。
▶︎ 解消: Nginx のルーティングに API 専用のルートを追加したら解消
/etc/nginx/conf.d/default.confserver { # http を https にリダイレクト listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name localhost; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # *** 追加: 以下の API 用ルーティングを追加 ↓↓↓ ****** location /my-app/api/ { proxy_pass http://127.0.0.1:3000/api/; proxy_redirect default; } # *** 追加 ↑↑↑ ********************************* location /my-app/ { proxy_pass http://127.0.0.1:3000/; proxy_redirect default; } location / { root /var/www; index index.php index.html index.htm; } # 404, 50x 等 エラー系のルートが以下にある }設定後、
nginx -s reloadで Nginx を再起動。
Angualar も再デプロイして起動し直し、ブラウザから再度アクセスしてみたら、
エラーは出なくなり、問題なく目的の JSON リストデータが返却されて、画面に表示された。ハマりどころ
そもそも「Angular の設定方法やロジックに原因がある」として、ずーーーーっと、アプリ設定のネット記事を漁っていたが、そこが間違いだった。
原因の切り分けフロー
HTTP 結果は 200 で、エラーの吐き方が、JSON パースエラー: クラサバ通信はできている
→ リクエストとレスポンスの機構を改めて確認: SSR で実装しているので、リクエスト/レスポンスは HTML を返却するのと JSON データを返却する2種類がある
→ ローカル環境で、ブラウザに GET メソッドの API を叩いてみる: JSON データが返り、画面に表示されることを確認
→ 公開サーバで、同じくブラウザに GET メソッドの API を叩いてみる: トップページが再表示された = HTML が返却された
→ Web サーバのリクエスト URI に対するルーティング先を修正すべきことに気づく
→ Nginx の config を修正して動作確認
→ 問題解消 !!!こんな感じだった。
エラーの切り分けを丁寧に行い、対応していくことは大事だ。
以上、Web サービスの設定は、大した内容ではないが間違えると結構ハマる。