Flutterでプロキシ設定を検出する 

## はじめに

プロキシが設定されている場合、Webブラウザの場合は設定したプロキシ設定を使って、自動に通信してくれますが、Flutterでは自分でプロキシの設定を検出して、処理します。

パッケージで解決しているのがないかなと検索したら、大体iOS/Android対応で、デスクトップ環境に対応したものを見つけられなかったので、[detect_proxy_setting](https://pub.dev/packages/detect_proxy_setting)を作成しました。

今回、どのような手順で作ったのかをまとめてみました。

## プラグイン用のプロジェクトを作成

ネイティブのコードを書く場合は、プラグインの開発となります。コマンドで簡単にプラグイン開発の雛形が作成できます。以下の例は、android,ios,macos,Windows用のひな形を作り、iosはswift、androidはkotlinで開発するように指定しています。

```shell
flutter create --org '<ネームスペース>'
 --template=plugin --platforms=android,ios,macos,windows
 -i swift -a kotlin `<パッケージ名>`
```

コマンドを実行すると、各環境のバージョン情報を返すネイティブのコードが書かれたプラグインが作られるので、これをもとに書き換えています。

本来であれば、この流れに従って作るんですが、自分で作成したのは、[url_launcher](https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher)を参考にして作りました。url_launcherは環境ごとのパッケージを用意して、まとまったものを一つのパッケージとして公開しています。

[melos](https://pub.dev/packages/melos)を使ったみたかったのもあり、パッケージを分ける構成にしています。melosは、lernaのdart版で、複数のパッケージを単一のリポジトリを管理するためのツールで、パッケージの公開やテストをまとめて処理できます。

```shell
melos test # テスト
melos publish # 公開(dry-run)
```

## プロキシ設定の取得

まずは、デフォルトのプロキシ設定を取得する方法を紹介します。MacOSとiOSはSwiftで同じコードが使えるので、まとめています。

```swift:iOS, MacOS
guard let setting = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
  return
}
```

```kotlin:Android
val connectivityManager = flutterPluginBinding.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val setting = connectivityManager?.getDefaultProxy()
```

```C++:Windows
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig;
BOOL result = WinHttpGetDefaultProxyConfiguration(&proxyConfig);
if (result == FALSE) {
  return;
}
WINHTTP_PROXY_INFO proxyInfo; 
result = WinHttpGetIEProxyConfigForCurrentUser(&proxyInfo);
if (result == FALSE) {
    return;
}
```

次に、URLを元にプロキシを適用するかどうか調べる方法です。

```swift:iOS, MacOS
guard let proxySetting = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
  return
}
guard let proxies = CFNetworkCopyProxiesForURL(url, proxySetting).takeRetainedValue()
 as? [[String: Any]], proxies.count == 0 {
  return
}

```

```kotlin:Android
val proxies = ProxySelector.getDefault().select(url)
if (proxies.size == 0) return
```

```C++:Windows
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig;
BOOL result = WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);
if (result == FALSE) {
  return;
}

HINTERNET hSession = WinHttpOpen(nullptr,
 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 WINHTTP_NO_PROXY_NAME,
 WINHTTP_NO_PROXY_BYPASS,
 WINHTTP_FLAG_ASYNC);

WINHTTP_AUTOPROXY_OPTIONS proxyOptions;
ZeroMemory(&proxyOptions, sizeof(WINHTTP_AUTOPROXY_OPTIONS));
if (proxyConfig.fAutoDetect) {
  proxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
  proxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
} else if(proxyConfig.lpszAutoConfigUrl) {
  proxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
  proxyOptions.lpszAutoConfigUrl = proxyConfig.lpszAutoConfigUrl;
}

WINHTTP_PROXY_INFO proxyInfo; 
result = WinHttpGetProxyForUrl(hSession, url, proxyOptions, &proxyInfo);
if (result == FALSE) {
    return;
}
```

## テスト

各パッケージに分かれているテストを動かして、動作確認すればいいんですが、今回作ったものがネットワークを利用するもので、テストを動かすことができず、サンプルプログラムで実際に呼び出して、動作を確認する手法をとりました。一つの環境が動けば、それに合わせて出力を合わせればいいので、OSの部分でどう返すのかを調べれば、なんとかなります。

## 公開
各環境で動くことが確認できたので、パッケージを公開します。しかし、僕はここで大きくミスを犯しました。パッケージ名は似たようなパッケージ名は使えません。公開すると消すこともできません。僕は、dry-runで特に問題ないので、そのまま公開作業をしたら、パッケージ名が近いということで、メインのパッケージだけ拒絶され、それ以外が公開されるという自体になりました。

- proxy_setting x
- [proxy_setting_windows](https://pub.dev/packages/proxy_setting_windows) ○
- [proxy_setting_macos](https://pub.dev/packages/proxy_setting_macos) ○
- [proxy_setting_platform_interface](https://pub.dev/packages/proxy_setting_platform_interface) ○

:::note alert
パッケージ名を決めるときに、最初に類似の物がないか、必ず検索してください。
:::

結局、メインだけ[detect_proxy_setting](https://pub.dev/packages/detect_proxy_setting)と変えて、それ以外はそのまま公開しています。各プラットフォーム別のパッケージは検索が引っかからないようにしています。


### 最後に

4/27に[Flutter × Kotlin Multiplatform by CyberAgent #6](https://cyberagent.connpass.com/event/243404/)のイベントを開催します。

Flutter for Windowsの話をして、アプリ開発をどのように行うか話す予定です。オンラインでの開催なので、お気軽にご参加ください。よろしくお願いいたします。