はじめに
プロキシが設定されている場合、Webブラウザの場合は設定したプロキシ設定を使って、自動に通信してくれますが、Flutterでは自分でプロキシの設定を検出して、処理します。
パッケージで解決しているのがないかなと検索したら、大体iOS/Android対応で、デスクトップ環境に対応したものを見つけられなかったので、detect_proxy_settingを作成しました。
今回、どのような手順で作ったのかをまとめてみました。
プラグイン用のプロジェクトを作成
ネイティブのコードを書く場合は、プラグインの開発となります。コマンドで簡単にプラグイン開発の雛形が作成できます。以下の例は、android,ios,macos,Windows用のひな形を作り、iosはswift、androidはkotlinで開発するように指定しています。
flutter create --org '<ネームスペース>'
--template=plugin --platforms=android,ios,macos,windows
-i swift -a kotlin `<パッケージ名>`
コマンドを実行すると、各環境のバージョン情報を返すネイティブのコードが書かれたプラグインが作られるので、これをもとに書き換えています。
本来であれば、この流れに従って作るんですが、自分で作成したのは、url_launcherを参考にして作りました。url_launcherは環境ごとのパッケージを用意して、まとまったものを一つのパッケージとして公開しています。
melosを使ったみたかったのもあり、パッケージを分ける構成にしています。melosは、lernaのdart版で、複数のパッケージを単一のリポジトリを管理するためのツールで、パッケージの公開やテストをまとめて処理できます。
melos test # テスト
melos publish # 公開(dry-run)
プロキシ設定の取得
まずは、デフォルトのプロキシ設定を取得する方法を紹介します。MacOSとiOSはSwiftで同じコードが使えるので、まとめています。
guard let setting = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
return
}
val connectivityManager = flutterPluginBinding.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val setting = connectivityManager?.getDefaultProxy()
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を元にプロキシを適用するかどうか調べる方法です。
guard let proxySetting = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
return
}
guard let proxies = CFNetworkCopyProxiesForURL(url, proxySetting).takeRetainedValue()
as? [[String: Any]], proxies.count == 0 {
return
}
val proxies = ProxySelector.getDefault().select(url)
if (proxies.size == 0) return
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 ○
- proxy_setting_macos ○
- proxy_setting_platform_interface ○
パッケージ名を決めるときに、最初に類似の物がないか、必ず検索してください。
結局、メインだけdetect_proxy_settingと変えて、それ以外はそのまま公開しています。各プラットフォーム別のパッケージは検索が引っかからないようにしています。
最後に
4/27にFlutter × Kotlin Multiplatform by CyberAgent #6のイベントを開催します。
Flutter for Windowsの話をして、アプリ開発をどのように行うか話す予定です。オンラインでの開催なので、お気軽にご参加ください。よろしくお願いいたします。