きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Dockerで起動していたAPIにlocalhostで接続できなくなった

※ この話はDocker固有の話ではありません。雑文です。


1週間前まで正常に動いていたのに、急に動かなくなったのでびっくりしました。

環境

  • Windows
    • 11

結論

localhostがIPv6の::1に変換されていた。

状態

  • Dockerでローカル環境を構築していた
  • localhostでDocker Containerに対して接続していた
    • APIに接続できなくなった
    • DBは接続できた
  • APIのコンテナにログインして、「localhost」でcurlを実行すると正常に返却される
  • ホストのWindowsから「localhost」にアクセスすると、アクセスログすら出てこない
  • ホストのWindowsから「127.0.0.1」にアクセスすると正常な応答が返却される

対策

IPv6よりもIPv4の優先度が低いため、IPv4の優先度を上げる。

管理者権限のコマンドプロンプトで実行する必要があります。起動後に次のコマンドを実行してください。詳細は理解していないので、参考記事を見てください。

# 状態確認
netsh interface ipv6 show prefixpolicies

#        50      0  ::1/128     ← IPv6の設定
#        40      1  ::/0    ←IPv6の設定
#        35      4  ::ffff:0:0/96 ← IPv4の設定
#        30      2  2002::/16
#         5      5  2001::/32
#         3     13  fc00::/7
#         1     11  fec0::/10
#         1     12  3ffe::/16
#         1      3  ::/96
# IPv4の優先度を上げるコマンド
netsh interface ipv6 set prefixpolicy ::ffff:0:0/96 60 4

試していないけど対策できそう

hostsファイルで明示的に記載しておけば、意図的にIPv4だけに変換できるかも。

# hostsファイルのディレクトリ
C:\windows\system32\drivers\etc\hosts

…設定したはずだけど、再起動が必要なんだっけ…?よくわかりません。

127.0.0.1       localhost

理由

急に発生した理由は分かりません。

ただ、Windows10からIPv6を優先して解決する設定が入っていたようなので、遅かれ早かれこの挙動は踏んだと思います。

終わりに

5時間くらいこの件にハマってました。ネットワークに強い人ならすぐに解決できるんですかね…。

混乱する原因となったのは、APIは接続できないのに、DBには接続できるという状況になってしまったからでした。ちなみに、なぜDBは接続できたかというとGUIアプリ側がちゃんと対応していたからでした。

しかも、普段はLinuxコマンドをWindowsで使いたいので、ターミナルはGit Bashで実行していたのですが、WSL2で起動するGit BashだとIPv4で解決されるからまた混乱する要因に…。画像は左からコマンドプロンプト、PowerShell、Git Bashです。WSL2を深く理解していなかったから、ハマりました。

もっとネットワーク強くなりたい。

類似情報

参考情報

Javaでメソッド参照を使ったまま、ラムダ式で条件否定をする(Predicate.not)

私はラムダ式での可読性を上げるため、メソッド参照を使用することが多いです。ただ、メソッド参照を使用したまま条件否定する方法を知らなかったため、無名関数を使用して条件を否定していました。または、内部で条件否定をしたメソッドを別に用意していることもありました。

今回、メソッド参照を使用したまま条件否定する方法を知ったので、それを残します。

環境

  • Java
    • 17

対応

Predicate.notを使用する。

次のコードは、日本円の商品以外の合計を取得するコードです。

public record Currency(String unit, int value) {
  public boolean isJpy() {
    return "JPY".equals(unit);
  }
}

public record Item(List<Currency> currencyList) {
  public int sumNotOnlyJpy() {
    return currencyList.stream()
        // 日本円「以外」、という条件になる
        .filter(Predicate.not(Currency::isJpy))
        .mapToInt(Currency::value)
        .sum();
  }
}

今までに行っていた対応

無名関数

分かりやすいが、どうしても否定の「!」を見逃すことはある。

public int sumNotOnlyJpy() {
  return currencyList.stream()
    .filter(e -> !e.isJpy())
    .mapToInt(Currency::value)
    .sum();
}

否定メソッドを用意する

否定条件の使用率が高い場合は、こちらを使用することもあります。ただ、無駄にメソッド数が増えてしまうので、最初から考慮する必要はありません。

public record Currency(String unit, int value) {
  public boolean isJpy() {
    return "JPY".equals(unit);
  }
  public boolean isNotJpy() {
    return !isJpy();
  }
}
public record Item(List<Currency> currencyList) {
  public int sumNotOnlyJpy() {
    return currencyList.stream()
      .filter(Currency::isNotJpy)
      .mapToInt(Currency::value)
      .sum();
  }
}

備考

そもそもメソッド参照の方が可読性が上がる理由としては無駄な変数が出てこないため、余計な処理をしていないことが約束されているからです。

無駄な変数が必ずしも悪いわけではないのですが、私だと無名関数で処理する意図を読もうとしてしまうので、一瞬身構えてしまいます。慣れるまではメソッド参照は難しいですが、IntelliJ IDEAでは変換を提案してくれるので、ガンガンIDEの力を使っていきましょう。

public int sumNotOnlyJpy() {
  return currencyList.stream()
      .filter(e -> e.isJpy()))
      .mapToInt(Currency::value)
      .sum();
}

ソースコード

終わりに

1行で終わるような記事ではあるのですが、こういう地味な書き方は知る機会がなくて難しいですね。

PythonでJSONを比較したい(I want assert JSON by Python)

環境

  • Python
    • 3.9

ユースケース

PythonでAPI開発を行い、レスポンスのJSONを比較したい。

次のJSONが等価であることを期待する。

{
  "id": 100,
  "name": "kirimaru"
}
{
  "name": "kirimaru",
    "id": 100
}

できていないこと

ソート順を無視したJSONの配列の比較。

対応

JSONを直接比較するような方法は見つかりませんでした。その代わりに、1度辞書型に変換することで目的の比較ができます。

JSON形式の文字列から辞書型に変換するには次のコードで変換できます。

import json

json.loads(f"{JSON文字列}")

テストコード

実際には次のようにテストを記載することで確認しました。

import json

def test_01():
    actual = """
{
  "id": 100,
  "name": "kirimaru",
}
    """
    expected = """
{
  "name": "kirimaru",
  "id": 100,
}
    """

    assert json.loads(actual) == json.loads(expected)

エンドポイントを確認するテストでは、テストクライアントの返却値が辞書型ですので、次のように比較できることが多いと思います。

import pytest

@pytest.mark.asyncio
async def test_response_json(async_client):
    response = await async_client.get("/health/")
    # 期待値レスポンスJSON
    # {"Hello": "World"}
    assert response.json() == dict(Hello="World")

ソースコード

終わりに

あまりWEB APIをPythonで作るようなユースケースが少ないのか、レスポンスJSONを比較するような記事が見つからなくて困りました。

正直、辞書型が正しいレスポンスなのかわからないので、できればJSONで直接比較したいのですが…

類似情報

参考情報