Slack に Java のライブラリがどのようなリクエストを投げているかを調べるため Charles を使って HTTP トレースをとった話

# 概要
レガシーの Java アプリケーションが slack-api を使って通知していましたが、2020 年 3 月初旬になったら通知に失敗するようになりました。
Mac でトラブルシュートをするため、通知に失敗しているリクエストのペイロードがどのような内容であるかを確かめたいと考えました。
[Fiddler](https://qiita.com/yut-nagase/items/19be8d4df706f8373ff0) を使ってトレースを取ろうとしましたが、乗ってきてくれないので、[Charles](https://www.charlesproxy.com/) を使うことに。
curl では、[こちら](https://www.yoheim.net/blog.php?q=20180308) を参考に、localhost:8888 へのプロキシを -x で指定すると、Chales でログが取得できました。

```.sh
curl -x localhost:8888 -X POST --data-urlencode "payload={\"channel\": \"#general\", \"username\": \"gpedro Bot\", \"text\": \"これは #general に投稿されています。\", \"icon_emoji\": \":ghost:\"}" https://hooks.slack.com/services/<YOUR>/<TOKEN>
```

Java アプリケーションでは、どのように Charles で Http トレースをとるのか、というのがいまいちわからなかったので調べることにしました。

# やり方
私のアプリケーションは、[このライブラリ](https://github.com/gpedro/slack-webhook) を使っていてました。随分メンテナンスが放って置かれていますし、マイナー感がでていますので、このライブラリについては、この記事をご覧になられている方々はまちがいなく使われていないのではないかと思います。
ただ、[Proxy クラス](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/Proxy.html) やあるいはそれに相当する設定を実施して、なんらかの形で HTTP リクエストを Proxy 経由で送信する方法は、どのライブラリでも存在しているのではと考えています。[^1]
このライブラリを使う際についても、デフォルトで API をコールするインスタンスを生成するのではなく、[Proxy クラス](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/Proxy.html) を指定して、インスタンスを作ることができました。この方法でインスタンスを生成すると、Charles を使ってこのライブラリのリクエストの HTTP トレースをとることができました。もしも、トラブルシュートをしたいライブラリので、なんらかの形で参考になれば幸いです。

### Charles のインストール
[こちらの記事](https://qiita.com/ishigaki_hige/items/41ab31b002c8649be2c2) を参考にインストールします。キーチェーンに入った、Charles Proxy.... という証明書を信頼します。

### Charles の証明書をエクスポート
キーチェーンにて、Charles Proxy... の証明書を右クリック -> [Charles Proxy CA を書き出す] にて証明書をエクスポートします。
証明書の名前にはエスケープ文字が多く含まれるので、必要に応じて名前をシンプルなものに変更します。

### JDK のライブラリへ証明書インポート
[こちらの記事](https://qiita.com/nenokido2000/items/b36b6e5f0854d7d63ba6) の通り、インポートします。
私の場合は、AWS Corretto 8 を使っていたので下記でインポートしました。パスワードは上述の記事の通りでした。

```.sh
sudo keytool -import -trustcacerts -file ~/Documents/charles_proxy.cer  -keystore /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/jre/lib/security/cacerts -alias ca
```

### Proxy を指定してリクエストする
curl のときと同様に、Charles のポート 8888 を指定して、リクエストします。
このライブラリでは、

```.java
import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackMessage;

import java.net.InetSocketAddress;
import java.net.Proxy;

public class SlackExecute {

    public static void main(String[] args) {

        SlackMessage slackMessage = new SlackMessage(
                "#genearal",
                "gpedro Bot",
                "gpedro で Slack リクエストできますか"
        );
        slackMessage.setIcon(":ghost:");
        SlackApi api = new SlackApi(
                "https://hooks.slack.com/services/<SOME>/<TOKEN>", 
                new Proxy(
                    Proxy.Type.HTTP,
                    new InetSocketAddress("localhost", 8888)
                )
         );
        api.call(slackMessage);
    }
}
```
### Charles を起動してアプリケーションを実行
これで、Charles の左ペーンに、https://hooks.slack.com が現れて内容をトレースできます。

```.json
{
    "channel": "#genearal",
    "username": "gpedro Bot",
    "icon_emoji": ":ghost:",
    "unfurl_media": false,
    "unfurl_links": false,
    "link_names": false,
    "text": "gpedro でリクエストできますか"
}
```

[^1]: 例えば Spring 5 にはリクエストを行う [WebClient インターフェース](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html) があり、[この記事](https://stackoverflow.com/questions/46979982/how-can-i-support-an-http-proxy-using-spring-5-webclient) にプロキシの指定の仕方が記載されています。このクラスの場合、[WebClient.Builder#clientConnector メソッド](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.Builder.html#clientConnector-org.springframework.http.client.reactive.ClientHttpConnector-)があります。このメソッドの引数には、[ClientHttpConnector クラス](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/reactive/ClientHttpConnector.html) が指定できます。この実装クラスの [ReactorClientHttpConnector](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/reactive/ReactorClientHttpConnector.html) の[コンストラクタ](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/reactive/ReactorClientHttpConnector.html#ReactorClientHttpConnector-reactor.netty.http.client.HttpClient-) には、[HttpClient](https://projectreactor.io/docs/netty/release/api/reactor/netty/http/client/HttpClient.html) が指定できます。[HttpClient](https://projectreactor.io/docs/netty/release/api/reactor/netty/http/client/HttpClient.html) の[tcpConfiguration メソッド](https://projectreactor.io/docs/netty/release/api/reactor/netty/http/client/HttpClient.html#tcpConfiguration-java.util.function.Function-) は、[TcpClient クラス](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/TcpClient.html) を引数にとる関数を引数にし、[TcpClient クラス](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/TcpClient.html) の、[proxy メソッド](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/TcpClient.html#proxy-java.util.function.Consumer-) は、[ProxyProvider.TypeSpec](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/ProxyProvider.TypeSpec.html) の実装を引数とする関数を引数に取ります。[ProxyProvider.TypeSpec インターフェースの type メソッド](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/ProxyProvider.TypeSpec.html#type-reactor.netty.tcp.ProxyProvider.Proxy-) で、Proxy のタイプを、[ProxyProvider.TypeSpec インターフェースの type メソッド](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/ProxyProvider.TypeSpec.html#type-reactor.netty.tcp.ProxyProvider.Proxy-)の戻り値の [ProxyProvider.AddressSpec](https://projectreactor.io/docs/netty/release/api/reactor/netty/tcp/ProxyProvider.AddressSpec.html) の実装で、ホストまたは [InetSocketAddress クラス](https://docs.oracle.com/javase/jp/8/docs/api/java/net/InetSocketAddress.html) を指定できます。[Proxy クラス](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/Proxy.html) を使いませんが、同じことを実現しています。